From a710246081cc2b87e7651c9c991db4c67dc49674 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sat, 1 May 2021 14:58:09 +0530 Subject: [PATCH 01/19] abstracted event_handler and config to make developing gui easier --- fishy/engine/common/event_handler.py | 23 ++++++++++++++++++++++- fishy/gui/config_top.py | 8 ++++++++ fishy/gui/gui.py | 4 ++-- fishy/helper/config.py | 7 ++++++- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/fishy/engine/common/event_handler.py b/fishy/engine/common/event_handler.py index 612a673..c8105a5 100644 --- a/fishy/engine/common/event_handler.py +++ b/fishy/engine/common/event_handler.py @@ -5,8 +5,29 @@ from fishy.engine import SemiFisherEngine from fishy.engine.fullautofisher.engine import FullAuto -class EngineEventHandler: +class IEngineHandler: + def __init__(self): + ... + + def start_event_handler(self): + ... + + def toggle_semifisher(self): + ... + + def toggle_fullfisher(self): + ... + + def check_pixel_val(self): + ... + + def quit(self): + ... + + +class EngineEventHandler(IEngineHandler): def __init__(self, gui_ref): + super().__init__() self.event_handler_running = True self.event = [] diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index 5001347..f8bc6c7 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -3,6 +3,7 @@ import os import typing from tkinter.filedialog import askopenfilename +from fishy.engine.common.event_handler import IEngineHandler from fishy.helper import helper from fishy import web @@ -109,3 +110,10 @@ def start_semifisher_config(gui: 'GUI'): controls_frame.pack(padx=(5, 5), pady=(5, 5)) top.start() + + +if __name__ == '__main__': + from fishy.gui import GUI + gui = GUI(lambda: IEngineHandler()) + gui.call_in_thread(lambda: start_fullfisher_config(gui)) + gui.create() diff --git a/fishy/gui/gui.py b/fishy/gui/gui.py index 79d8821..14d5f45 100644 --- a/fishy/gui/gui.py +++ b/fishy/gui/gui.py @@ -8,7 +8,7 @@ import threading from fishy.web import web from ttkthemes import ThemedTk -from fishy.engine.common.event_handler import EngineEventHandler +from fishy.engine.common.event_handler import EngineEventHandler, IEngineHandler from fishy.gui import config_top from fishy.gui.funcs import GUIFuncs from . import main_gui @@ -18,7 +18,7 @@ from ..helper.helper import wait_until class GUI: - def __init__(self, get_engine: Callable[[], EngineEventHandler]): + def __init__(self, get_engine: Callable[[], IEngineHandler]): self.funcs = GUIFuncs(self) self.get_engine = get_engine diff --git a/fishy/helper/config.py b/fishy/helper/config.py index 896fbc8..838bde3 100644 --- a/fishy/helper/config.py +++ b/fishy/helper/config.py @@ -107,7 +107,7 @@ class config: :param default: default value to return if key is not found :return: config value """ - return default if config._instance[key] is None else config._instance[key] + return default if config._instance is None or config._instance[key] is None else config._instance[key] @staticmethod def set(key, value, save=True): @@ -117,6 +117,9 @@ class config: :param value: value to save :param save: False if don't want to save right away """ + if config._instance is None: + return + config._instance[key] = value if save: config.save_config() @@ -135,4 +138,6 @@ class config: @staticmethod def save_config(): + if config._instance is None: + return config._instance.save_config() From 73a0500cdf356a89852792027de1dd80387a04a8 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sat, 1 May 2021 14:59:12 +0530 Subject: [PATCH 02/19] added mode option and recalibrate option --- fishy/gui/config_top.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index f8bc6c7..f0d9b5f 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -39,10 +39,25 @@ def start_fullfisher_config(gui: 'GUI'): file_name_label.set(file_name()) + def start_calibrate(): + ... + + def select_radio(): + ... + file_name_label = StringVar(value=file_name()) - Label(controls_frame, textvariable=file_name_label).grid(row=0, column=0) - Button(controls_frame, text="Select fishy file", command=select_file).grid(row=0, column=1) - Label(controls_frame, text="Use semi-fisher config for rest").grid(row=2, column=0, columnspan=2) + radio_var = IntVar() + + Button(controls_frame, text="Recalibrate", command=start_calibrate).grid(row=0, column=0, columnspan=2, pady=(5, 5)) + + Label(controls_frame, text="Mode: ").grid(row=1, column=0, pady=(5, 0)) + Radiobutton(controls_frame, text="Player", variable=radio_var, value=0, command=select_radio).grid(row=1, column=1, sticky="w") + Radiobutton(controls_frame, text="Recorder", variable=radio_var, value=1, command=select_radio).grid(row=2, column=1, sticky="w", pady=(0, 5)) + + Button(controls_frame, text="Select fishy file", command=select_file).grid(row=3, column=0, columnspan=2, pady=(5, 0)) + Label(controls_frame, textvariable=file_name_label).grid(row=4, column=0, columnspan=2, pady=(0, 5)) + + Label(controls_frame, text="Use semi-fisher config for rest").grid(row=5, column=0, columnspan=2, pady=(15, 5)) controls_frame.pack(padx=(5, 5), pady=(5, 5)) top.start() From 1290c877f158b46369d598aaba4548485593ae95 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sat, 1 May 2021 15:04:38 +0530 Subject: [PATCH 03/19] removed controls from engine --- fishy/engine/fullautofisher/controls.py | 19 +++++-------------- fishy/engine/fullautofisher/engine.py | 5 ----- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/fishy/engine/fullautofisher/controls.py b/fishy/engine/fullautofisher/controls.py index a05cb1b..d38fb9c 100644 --- a/fishy/engine/fullautofisher/controls.py +++ b/fishy/engine/fullautofisher/controls.py @@ -1,27 +1,18 @@ import logging +from pynput.keyboard import Key + from fishy.helper import hotkey from fishy.engine.fullautofisher.engine import FullAuto, State -from fishy.helper.hotkey import Key -def get_controls(engine: FullAuto): - from fishy.engine.fullautofisher.recorder import Recorder - from fishy.engine.fullautofisher.player import Player +def get_controls(controls: 'Controls'): controls = [ ("MODE_SELECT", { - Key.RIGHT: (Recorder(engine).toggle_recording, "start/stop record"), - Key.UP: (engine.calibrator.calibrate, "calibrate mode"), - Key.LEFT: (Player(engine).toggle_move, "start/stop play"), - Key.DOWN: (lambda: engine.controls.select_mode("TEST1"), "test mode"), + Key.DOWN: (lambda: controls.select_mode("TEST1"), "test mode"), }), - ("TEST1", { - Key.RIGHT: (engine.test.print_coods, "print coordinates"), - Key.UP: (engine.test.look_for_hole, "look for hole up down"), - Key.LEFT: (engine.test.set_target, "set target"), - Key.DOWN: (engine.test.move_to_target, "move to target") - }) + ("TEST1", {}) ] return controls diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index dea0233..81f65ce 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -46,8 +46,6 @@ class FullAuto(IEngine): state = State.NONE def __init__(self, gui_ref): - from fishy.engine.fullautofisher.controls import Controls - from fishy.engine.fullautofisher import controls from fishy.engine.fullautofisher.calibrator import Calibrator from fishy.engine.fullautofisher.test import Test @@ -58,7 +56,6 @@ class FullAuto(IEngine): self.fisher = SemiFisherEngine(None) self.calibrator = Calibrator(self) self.test = Test(self) - self.controls = Controls(controls.get_controls(self)) self.show_crop = False def run(self): @@ -86,7 +83,6 @@ class FullAuto(IEngine): self.fisher.toggle_start() fishing_event.unsubscribe() - self.controls.initialize() while self.start and WindowClient.running(): if self.show_crop: self.window.show(self.show_crop, func=image_pre_process) @@ -99,7 +95,6 @@ class FullAuto(IEngine): logging.error("Game window not found") self.gui.bot_started(False) - self.controls.unassign_keys() self.window.show(False) logging.info("Quitting") self.window.destory() From 3ce3c24dd105289a4af01c87ed20a365dd4ba087 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sun, 2 May 2021 21:09:59 +0530 Subject: [PATCH 04/19] connected mode select in config with the engine - restructed player and recorder to work with new system, - remove FullAuto.State --- fishy/engine/fullautofisher/controls.py | 6 -- fishy/engine/fullautofisher/engine.py | 55 ++++++----- fishy/engine/fullautofisher/mode/__init__.py | 0 fishy/engine/fullautofisher/mode/imode.py | 14 +++ fishy/engine/fullautofisher/mode/player.py | 92 ++++++++++++++++++ .../fullautofisher/{ => mode}/recorder.py | 24 ++--- fishy/engine/fullautofisher/player.py | 95 ------------------- fishy/gui/config_top.py | 13 +-- fishy/helper/helper.py | 25 +++++ 9 files changed, 174 insertions(+), 150 deletions(-) create mode 100644 fishy/engine/fullautofisher/mode/__init__.py create mode 100644 fishy/engine/fullautofisher/mode/imode.py create mode 100644 fishy/engine/fullautofisher/mode/player.py rename fishy/engine/fullautofisher/{ => mode}/recorder.py (75%) delete mode 100644 fishy/engine/fullautofisher/player.py diff --git a/fishy/engine/fullautofisher/controls.py b/fishy/engine/fullautofisher/controls.py index d38fb9c..e18db67 100644 --- a/fishy/engine/fullautofisher/controls.py +++ b/fishy/engine/fullautofisher/controls.py @@ -4,8 +4,6 @@ from pynput.keyboard import Key from fishy.helper import hotkey -from fishy.engine.fullautofisher.engine import FullAuto, State - def get_controls(controls: 'Controls'): controls = [ @@ -36,10 +34,6 @@ class Controls: logging.info(help_str) def select_mode(self, mode): - if FullAuto.state != State.NONE: - self.log_help() - return - self.current_menu = 0 for i, control in enumerate(self.controls): if mode == control[0]: diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 81f65ce..4cb0b5c 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -8,6 +8,9 @@ import logging import time from fishy.constants import libgps, fishyqr, lam2 +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_values_from_image, get_qr_location from fishy.engine.semifisher.fishing_mode import FishingMode @@ -19,7 +22,8 @@ from fishy.engine.common.IEngine import IEngine from pynput import keyboard, mouse from fishy.helper import hotkey, helper -from fishy.helper.helper import sign +from fishy.helper.helper import sign, log_raise +from fishy.helper.config import config mse = mouse.Controller() kb = keyboard.Controller() @@ -34,16 +38,8 @@ def image_pre_process(img): return img -class State(Enum): - NONE = 0 - PLAYING = 1 - RECORDING = 2 - OTHER = 3 - - class FullAuto(IEngine): rotate_by = 30 - state = State.NONE def __init__(self, gui_ref): from fishy.engine.fullautofisher.calibrator import Calibrator @@ -58,6 +54,8 @@ class FullAuto(IEngine): self.test = Test(self) self.show_crop = False + self.mode = None + def run(self): addons_req = [libgps, lam2, fishyqr] @@ -65,34 +63,33 @@ class FullAuto(IEngine): if not helper.addon_exists(*addon): helper.install_addon(*addon) - FullAuto.state = State.NONE - self.gui.bot_started(True) self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="Full auto debug") + self.mode = Player(self) if FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Player else Recorder(self) + # todo use config to run player or recorder + # noinspection PyBroadException try: + if self.window.get_capture() is None: + log_raise("Game window not found") + self.window.crop = get_qr_location(self.window.get_capture()) if self.window.crop is None: - logging.warning("FishyQR not found") - self.start = False - raise Exception("FishyQR not found") + log_raise("FishyQR not found") if not self.calibrator.all_callibrated(): - logging.error("you need to calibrate first") + log_raise("you need to calibrate first") self.fisher.toggle_start() fishing_event.unsubscribe() + if self.show_crop: + self.start_show() - while self.start and WindowClient.running(): - if self.show_crop: - self.window.show(self.show_crop, func=image_pre_process) - else: - time.sleep(0.1) - except: + self.mode.run() + + except Exception: traceback.print_exc() - - if self.window.get_capture() is None: - logging.error("Game window not found") + self.start = False self.gui.bot_started(False) self.window.show(False) @@ -100,6 +97,12 @@ class FullAuto(IEngine): self.window.destory() self.fisher.toggle_start() + def start_show(self): + def func(): + while self.start and WindowClient.running(): + self.window.show(self.show_crop, func=image_pre_process) + Thread(target=func).start() + def get_coods(self): img = self.window.processed_image(func=image_pre_process) return get_values_from_image(img) @@ -191,10 +194,6 @@ class FullAuto(IEngine): self._curr_rotate_y -= 0.05 def toggle_start(self): - if self.start and FullAuto.state != State.NONE: - logging.info("Please turn off RECORDING/PLAYING first") - return - self.start = not self.start if self.start: self.thread = Thread(target=self.run) diff --git a/fishy/engine/fullautofisher/mode/__init__.py b/fishy/engine/fullautofisher/mode/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fishy/engine/fullautofisher/mode/imode.py b/fishy/engine/fullautofisher/mode/imode.py new file mode 100644 index 0000000..f1dc2ef --- /dev/null +++ b/fishy/engine/fullautofisher/mode/imode.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod +from enum import Enum + + +class FullAutoMode(Enum): + Player = 0 + Recorder = 1 + + +class IMode(ABC): + + @abstractmethod + def run(self): + ... diff --git a/fishy/engine/fullautofisher/mode/player.py b/fishy/engine/fullautofisher/mode/player.py new file mode 100644 index 0000000..8d22d9f --- /dev/null +++ b/fishy/engine/fullautofisher/mode/player.py @@ -0,0 +1,92 @@ +import logging +import pickle +from pprint import pprint + +import typing +from threading import Thread + +from fishy.engine.fullautofisher.mode.imode import IMode +from fishy.engine.semifisher import fishing_event, fishing_mode +from fishy.helper.helper import log_raise, wait_until, kill_thread + +if typing.TYPE_CHECKING: + from fishy.engine.fullautofisher.engine import FullAuto + +from fishy.helper import helper +from fishy.helper.config import config + + +def _get_rec_file(): + file = config.get("full_auto_rec_file") + + if not file: + logging.error("Please select a fishy file first from config") + return None + + file = open(file, 'rb') + data = pickle.load(file) + file.close() + pprint(data) + if "full_auto_path" not in data: + logging.error("invalid file") + return None + return data["full_auto_path"] + + +class Player(IMode): + def __init__(self, engine: 'FullAuto'): + self.recording = False + self.engine = engine + self.hole_complete_flag = False + self.start_moving_flag = False + self.i = 0 + self.forward = True + self.timeline = None + + def run(self): + self._init() + while self.engine.start: + self._loop() + logging.info("player stopped") + + def _init(self): + self.timeline = _get_rec_file() + if not self.timeline: + log_raise("data not found, can't start") + logging.info("starting player") + + def _loop(self): + action = self.timeline[self.i] + + if action[0] == "move_to": + self.engine.move_to(action[1]) + elif action[0] == "check_fish": + self.engine.move_to(action[1]) + self.engine.rotate_to(action[1][2]) + fishing_event.subscribe() + fishing_mode.subscribers.append(self._hole_complete_callback) + # scan for fish hole + logging.info("scanning") + # if found start fishing and wait for hole to complete + if self.engine.look_for_hole(): + logging.info("starting fishing") + self.hole_complete_flag = False + helper.wait_until(lambda: self.hole_complete_flag or not self.engine.start) + self.engine.rotate_back() + else: + logging.info("no hole found") + # continue when hole completes + fishing_event.unsubscribe() + fishing_mode.subscribers.remove(self._hole_complete_callback) + + self.i += 1 if self.forward else -1 + if self.i >= len(self.timeline): + self.forward = False + self.i = len(self.timeline) - 1 + elif self.i < 0: + self.forward = True + self.i = 0 + + def _hole_complete_callback(self, e): + if e == fishing_event.State.IDLE: + self.hole_complete_flag = True diff --git a/fishy/engine/fullautofisher/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py similarity index 75% rename from fishy/engine/fullautofisher/recorder.py rename to fishy/engine/fullautofisher/mode/recorder.py index 279f69b..3fd4f81 100644 --- a/fishy/engine/fullautofisher/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -4,17 +4,21 @@ import time from pprint import pprint from tkinter.filedialog import asksaveasfile -from fishy.engine.fullautofisher.engine import FullAuto, State +import typing + +if typing.TYPE_CHECKING: + from fishy.engine.fullautofisher.engine import FullAuto +from fishy.engine.fullautofisher.mode.imode import IMode from fishy.helper.hotkey import Key from fishy.helper.hotkey_process import HotKey -class Recorder: +class Recorder(IMode): recording_fps = 1 mark_hole_key = Key.F8 - def __init__(self, engine: FullAuto): + def __init__(self, engine: 'FullAuto'): self.recording = False self.engine = engine self.timeline = [] @@ -24,23 +28,14 @@ class Recorder: self.timeline.append(("check_fish", coods)) logging.info("check_fish") - def toggle_recording(self): - if FullAuto.state != State.RECORDING and FullAuto.state != State.NONE: - return - - self.recording = not self.recording - if self.recording: - self._start_recording() - - def _start_recording(self): - FullAuto.state = State.RECORDING + def run(self): logging.info("starting, press f8 to mark hole") hk = HotKey() hk.start_process(self._mark_hole) self.timeline = [] - while self.recording: + while self.engine.start: start_time = time.time() coods = None while not coods: @@ -68,5 +63,4 @@ class Recorder: pprint(data) pickle.dump(data, file) file.close() - FullAuto.state = State.NONE diff --git a/fishy/engine/fullautofisher/player.py b/fishy/engine/fullautofisher/player.py deleted file mode 100644 index 402ebb8..0000000 --- a/fishy/engine/fullautofisher/player.py +++ /dev/null @@ -1,95 +0,0 @@ -import logging -import pickle -from pprint import pprint - -from fishy.engine.semifisher import fishing_event, fishing_mode - -from fishy.engine.fullautofisher.engine import FullAuto, State - -from fishy.helper import helper -from fishy.helper.config import config - - -def _get_rec_file(): - file = config.get("full_auto_rec_file") - - if not file: - logging.error("Please select a fishy file first from config") - return None - - file = open(file, 'rb') - data = pickle.load(file) - file.close() - pprint(data) - if "full_auto_path" not in data: - logging.error("invalid file") - return None - return data["full_auto_path"] - - -class Player: - def __init__(self, engine: 'FullAuto'): - self.recording = False - self.engine = engine - self.hole_complete_flag = False - self.start_moving_flag = False - - def toggle_move(self): - if FullAuto.state != State.PLAYING and FullAuto.state != State.NONE: - return - - self.start_moving_flag = not self.start_moving_flag - if self.start_moving_flag: - self._start_route() - else: - logging.info("Waiting for the last action to finish...") - - def _hole_complete_callback(self, e): - if e == fishing_event.State.IDLE: - self.hole_complete_flag = True - - def _start_route(self): - timeline = _get_rec_file() - if not timeline: - logging.log("data not found, can't start") - return - - FullAuto.state = State.PLAYING - logging.info("starting to move") - - forward = True - i = 0 - while self.start_moving_flag: - action = timeline[i] - - if action[0] == "move_to": - self.engine.move_to(action[1]) - elif action[0] == "check_fish": - self.engine.move_to(action[1]) - self.engine.rotate_to(action[1][2]) - fishing_event.subscribe() - fishing_mode.subscribers.append(self._hole_complete_callback) - # scan for fish hole - logging.info("scanning") - if self.engine.look_for_hole(): - logging.info("starting fishing") - self.hole_complete_flag = False - helper.wait_until(lambda: self.hole_complete_flag or not self.start_moving_flag) - self.engine.rotate_back() - else: - logging.info("no hole found") - # if found start fishing and wait for hole to complete - # contine when hole completes - fishing_event.unsubscribe() - fishing_mode.subscribers.remove(self._hole_complete_callback) - - i += 1 if forward else -1 - if i >= len(timeline): - forward = False - i = len(timeline) - 1 - elif i < 0: - forward = True - i = 0 - - logging.info("stopped") - FullAuto.state = State.NONE diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index f0d9b5f..00cb3c6 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -4,6 +4,7 @@ import typing from tkinter.filedialog import askopenfilename from fishy.engine.common.event_handler import IEngineHandler +from fishy.engine.fullautofisher.mode.imode import FullAutoMode from fishy.helper import helper from fishy import web @@ -42,17 +43,17 @@ def start_fullfisher_config(gui: 'GUI'): def start_calibrate(): ... - def select_radio(): - ... + def mode_command(): + config.set("full_auto_mode", mode_var.get()) file_name_label = StringVar(value=file_name()) - radio_var = IntVar() + mode_var = IntVar(value=config.get("full_auto_mode", 0)) - Button(controls_frame, text="Recalibrate", command=start_calibrate).grid(row=0, column=0, columnspan=2, pady=(5, 5)) + Button(controls_frame, text="Calibrate", command=start_calibrate).grid(row=0, column=0, columnspan=2, pady=(5, 5)) Label(controls_frame, text="Mode: ").grid(row=1, column=0, pady=(5, 0)) - Radiobutton(controls_frame, text="Player", variable=radio_var, value=0, command=select_radio).grid(row=1, column=1, sticky="w") - Radiobutton(controls_frame, text="Recorder", variable=radio_var, value=1, command=select_radio).grid(row=2, column=1, sticky="w", pady=(0, 5)) + Radiobutton(controls_frame, text="Player", variable=mode_var, value=FullAutoMode.Player.value, command=mode_command).grid(row=1, column=1, sticky="w") + Radiobutton(controls_frame, text="Recorder", variable=mode_var, value=FullAutoMode.Recorder.value, command=mode_command).grid(row=2, column=1, sticky="w", pady=(0, 5)) Button(controls_frame, text="Select fishy file", command=select_file).grid(row=3, column=0, columnspan=2, pady=(5, 0)) Label(controls_frame, textvariable=file_name_label).grid(row=4, column=0, columnspan=2, pady=(0, 5)) diff --git a/fishy/helper/helper.py b/fishy/helper/helper.py index fb0b591..55af70a 100644 --- a/fishy/helper/helper.py +++ b/fishy/helper/helper.py @@ -1,3 +1,4 @@ +import ctypes import logging import os import shutil @@ -202,8 +203,32 @@ def restart(): os.execl(sys.executable, *([sys.executable] + sys.argv)) +def log_raise(msg): + logging.error(msg) + raise Exception(msg) + + def update(): from .config import config config.delete("dont_ask_update") restart() + + +# noinspection PyProtectedMember,PyUnresolvedReferences +def _get_id(thread): + # returns id of the respective thread + if hasattr(thread, '_thread_id'): + return thread._thread_id + for id, thread in threading._active.items(): + if thread is thread: + return id + + +def kill_thread(thread): + thread_id = _get_id(thread) + res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, + ctypes.py_object(SystemExit)) + if res > 1: + ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) + print('Exception raise failure') From 6440ec1000db2e9f1d625f59d2c61b9c021e5c81 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sun, 2 May 2021 22:19:44 +0530 Subject: [PATCH 05/19] calibrate connected to config --- fishy/engine/fullautofisher/engine.py | 14 ++++++++++---- .../fullautofisher/{ => mode}/calibrator.py | 19 +++++++++++++------ fishy/gui/config_top.py | 4 +++- 3 files changed, 26 insertions(+), 11 deletions(-) rename fishy/engine/fullautofisher/{ => mode}/calibrator.py (84%) diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 4cb0b5c..d57cea2 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -1,6 +1,5 @@ import math import traceback -from enum import Enum from threading import Thread import cv2 @@ -8,6 +7,7 @@ import logging import time from fishy.constants import libgps, fishyqr, lam2 +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 @@ -42,7 +42,7 @@ class FullAuto(IEngine): rotate_by = 30 def __init__(self, gui_ref): - from fishy.engine.fullautofisher.calibrator import Calibrator + from fishy.engine.fullautofisher.mode.calibrator import Calibrator from fishy.engine.fullautofisher.test import Test super().__init__(gui_ref) @@ -65,9 +65,15 @@ class FullAuto(IEngine): self.gui.bot_started(True) self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="Full auto debug") - self.mode = Player(self) if FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Player else Recorder(self) - # todo use config to run player or recorder + self.mode = None + if config.get("calibrate", False): + self.mode = Calibrator(self) + elif FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Player: + self.mode = Player(self) + elif FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Recorder: + self.mode = Recorder(self) + # noinspection PyBroadException try: if self.window.get_capture() is None: diff --git a/fishy/engine/fullautofisher/calibrator.py b/fishy/engine/fullautofisher/mode/calibrator.py similarity index 84% rename from fishy/engine/fullautofisher/calibrator.py rename to fishy/engine/fullautofisher/mode/calibrator.py index a660529..a5cefc5 100644 --- a/fishy/engine/fullautofisher/calibrator.py +++ b/fishy/engine/fullautofisher/mode/calibrator.py @@ -4,10 +4,13 @@ import time import cv2 import numpy as np +import typing -from fishy.engine.fullautofisher.engine import FullAuto +if typing.TYPE_CHECKING: + from fishy.engine.fullautofisher.engine import FullAuto from pynput import keyboard, mouse +from fishy.engine.fullautofisher.mode.imode import IMode from fishy.helper.config import config mse = mouse.Controller() @@ -44,8 +47,8 @@ def _get_factor(key): return config.get("full_auto_factors", {}).get(key) -class Calibrator: - def __init__(self, engine: FullAuto): +class Calibrator(IMode): + def __init__(self, engine: 'FullAuto'): self._callibrate_state = -1 self.engine = engine @@ -86,9 +89,11 @@ class Calibrator: move_factor = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / walking_time _update_factor("move_factor", move_factor) - logging.info("done") + logging.info("walk calibrate done") def _rotate_calibrate(self): + from fishy.engine.fullautofisher.engine import FullAuto + rotate_times = 50 coods = self.engine.get_coods() @@ -110,9 +115,11 @@ class Calibrator: rot_factor = (rot3 - rot2) / rotate_times _update_factor("rot_factor", rot_factor) - logging.info("done") + logging.info("rotate calibrate done") - def calibrate(self): + def run(self): self._walk_calibrate() self._rotate_calibrate() + config.set("calibrate", False) + logging.info("calibration done") diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index 00cb3c6..029f78b 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -41,7 +41,9 @@ def start_fullfisher_config(gui: 'GUI'): file_name_label.set(file_name()) def start_calibrate(): - ... + top.quit_top() + config.set("calibrate", True) + gui.engine.toggle_fullfisher() def mode_command(): config.set("full_auto_mode", mode_var.get()) From b0a8db75282e3c5d984fb02b87f8a072e0d69835 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sun, 2 May 2021 23:35:06 +0530 Subject: [PATCH 06/19] wait for eso window active, stop engine when window becomes inactive --- fishy/engine/fullautofisher/engine.py | 15 ++++++++++++++- fishy/engine/semifisher/fishing_event.py | 4 +++- fishy/helper/helper.py | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index d57cea2..d3d5107 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -22,7 +22,7 @@ from fishy.engine.common.IEngine import IEngine from pynput import keyboard, mouse from fishy.helper import hotkey, helper -from fishy.helper.helper import sign, log_raise +from fishy.helper.helper import sign, log_raise, wait_until, is_eso_active from fishy.helper.config import config mse = mouse.Controller() @@ -74,6 +74,12 @@ class FullAuto(IEngine): elif FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Recorder: self.mode = Recorder(self) + if not is_eso_active(): + logging.info("Waiting for eso window to be active...") + wait_until(is_eso_active) + logging.info("starting in 2 secs...") + time.sleep(2) + # noinspection PyBroadException try: if self.window.get_capture() is None: @@ -90,6 +96,7 @@ class FullAuto(IEngine): fishing_event.unsubscribe() if self.show_crop: self.start_show() + self.stop_on_inactive() self.mode.run() @@ -109,6 +116,12 @@ class FullAuto(IEngine): self.window.show(self.show_crop, func=image_pre_process) Thread(target=func).start() + def stop_on_inactive(self): + def func(): + wait_until(lambda: not is_eso_active()) + self.start = False + Thread(target=func).start() + def get_coods(self): img = self.window.processed_image(func=image_pre_process) return get_values_from_image(img) diff --git a/fishy/engine/semifisher/fishing_event.py b/fishy/engine/semifisher/fishing_event.py index 895d566..2158d90 100644 --- a/fishy/engine/semifisher/fishing_event.py +++ b/fishy/engine/semifisher/fishing_event.py @@ -19,6 +19,8 @@ from fishy.helper.config import config import random +from fishy.helper.helper import is_eso_active + class FishEvent: fishCaught = 0 @@ -46,7 +48,7 @@ def _fishing_sleep(waittime, lower_limit_ms = 16, upper_limit_ms = 2500): def if_eso_is_focused(func): def wrapper(): - if GetWindowText(GetForegroundWindow()) != "Elder Scrolls Online": + if not is_eso_active(): logging.warning("ESO window is not focused") return func() diff --git a/fishy/helper/helper.py b/fishy/helper/helper.py index 55af70a..697c9c5 100644 --- a/fishy/helper/helper.py +++ b/fishy/helper/helper.py @@ -17,6 +17,7 @@ from hashlib import md5 from win32com.client import Dispatch from win32comext.shell import shell, shellcon +from win32gui import GetForegroundWindow, GetWindowText import fishy import winshell @@ -215,6 +216,10 @@ def update(): restart() +def is_eso_active(): + return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online" + + # noinspection PyProtectedMember,PyUnresolvedReferences def _get_id(thread): # returns id of the respective thread From bfb498d1c94a23c07ae7d945c4f9ba12b59a03c6 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sun, 2 May 2021 23:53:18 +0530 Subject: [PATCH 07/19] add sound feedback to recording hole --- fishy/engine/fullautofisher/mode/recorder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index 3fd4f81..f69fc27 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -6,6 +6,10 @@ from tkinter.filedialog import asksaveasfile import typing +from playsound import playsound + +from fishy import helper + if typing.TYPE_CHECKING: from fishy.engine.fullautofisher.engine import FullAuto from fishy.engine.fullautofisher.mode.imode import IMode @@ -26,6 +30,7 @@ class Recorder(IMode): def _mark_hole(self): coods = self.engine.get_coods() self.timeline.append(("check_fish", coods)) + playsound(helper.manifest_file("beep.wav"), False) logging.info("check_fish") def run(self): From aa207dae02ebcfe672b940f71f6b539116168d50 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Mon, 3 May 2021 00:09:33 +0530 Subject: [PATCH 08/19] fixed divide by zero error --- fishy/engine/fullautofisher/engine.py | 6 +++--- fishy/engine/fullautofisher/mode/calibrator.py | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index d3d5107..dc6bf90 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -89,7 +89,7 @@ class FullAuto(IEngine): if self.window.crop is None: log_raise("FishyQR not found") - if not self.calibrator.all_callibrated(): + if not self.calibrator.all_calibrated(): log_raise("you need to calibrate first") self.fisher.toggle_start() @@ -131,8 +131,8 @@ class FullAuto(IEngine): logging.error("set target first") return - if not self.calibrator.all_callibrated(): - logging.error("you need to callibrate first") + if not self.calibrator.all_calibrated(): + logging.error("you need to calibrate first") return current = self.get_coods() diff --git a/fishy/engine/fullautofisher/mode/calibrator.py b/fishy/engine/fullautofisher/mode/calibrator.py index a5cefc5..ec20ca6 100644 --- a/fishy/engine/fullautofisher/mode/calibrator.py +++ b/fishy/engine/fullautofisher/mode/calibrator.py @@ -62,8 +62,11 @@ class Calibrator(IMode): # endregion - def all_callibrated(self): - return self.move_factor is not None and self.rot_factor is not None + def all_calibrated(self): + return self.move_factor is not None and \ + self.rot_factor is not None and \ + self.move_factor != 0 and \ + self.rot_factor != 0 def toggle_show(self): self.engine.show_crop = not self.engine.show_crop From 65052f3fa3b079afd6650b888c73354ef263e319 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Mon, 3 May 2021 00:38:39 +0530 Subject: [PATCH 09/19] automatically load recording when it ends --- fishy/engine/fullautofisher/mode/recorder.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index f69fc27..4f25016 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -1,4 +1,5 @@ import logging +import os import pickle import time from pprint import pprint @@ -6,6 +7,7 @@ from tkinter.filedialog import asksaveasfile import typing +from fishy.helper.config import config from playsound import playsound from fishy import helper @@ -34,7 +36,7 @@ class Recorder(IMode): logging.info("check_fish") def run(self): - logging.info("starting, press f8 to mark hole") + logging.info("starting, press LMB to mark hole") hk = HotKey() hk.start_process(self._mark_hole) @@ -63,9 +65,10 @@ class Recorder(IMode): return _file - file = self.engine.get_gui().call_in_thread(func, block=True) + file: typing.BinaryIO = self.engine.get_gui().call_in_thread(func, block=True) data = {"full_auto_path": self.timeline} - pprint(data) pickle.dump(data, file) + config.set("full_auto_rec_file", file.name) + logging.info(f"saved {os.path.basename(file.name)} recording, and loaded it in player") file.close() From ab81d0974106b210294ba3d47fa5212ebaab6bdf Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Wed, 12 May 2021 21:07:20 +0530 Subject: [PATCH 10/19] ask whether to save or not when recording ends --- fishy/engine/fullautofisher/mode/recorder.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index 5e951ce..4da303e 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -2,6 +2,7 @@ import logging import os import pickle import time +from tkinter.messagebox import askyesno import typing from tkinter.filedialog import asksaveasfile @@ -53,16 +54,20 @@ class Recorder(IMode): logging.warning("Took too much time to record") hk.stop() + self._ask_to_save() + def _ask_to_save(self): def func(): _file = None files = [('Fishy File', '*.fishy')] - while not _file: + while not _file and askyesno("Save Recording?", "Would you like to save the recording?"): _file = asksaveasfile(mode='wb', filetypes=files, defaultextension=files) - return _file file: typing.BinaryIO = self.engine.get_gui().call_in_thread(func, block=True) + if not file: + return + data = {"full_auto_path": self.timeline} pickle.dump(data, file) config.set("full_auto_rec_file", file.name) From 0622531c2b5df41130e28de3a59968698e6953f0 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Thu, 13 May 2021 13:45:44 +0530 Subject: [PATCH 11/19] switched tk to ttk in config_top for fullauto --- fishy/gui/config_top.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index 6a98d5b..d6e3cac 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -50,16 +50,16 @@ def start_fullfisher_config(gui: 'GUI'): file_name_label = tk.StringVar(value=file_name()) mode_var = tk.IntVar(value=config.get("full_auto_mode", 0)) - tk.Button(controls_frame, text="Calibrate", command=start_calibrate).grid(row=0, column=0, columnspan=2, pady=(5, 5)) + ttk.Button(controls_frame, text="Calibrate", command=start_calibrate).grid(row=0, column=0, columnspan=2, pady=(5, 5)) - tk.Label(controls_frame, text="Mode: ").grid(row=1, column=0, pady=(5, 0)) - tk.Radiobutton(controls_frame, text="Player", variable=mode_var, value=FullAutoMode.Player.value, command=mode_command).grid(row=1, column=1, sticky="w") - tk.Radiobutton(controls_frame, text="Recorder", variable=mode_var, value=FullAutoMode.Recorder.value, command=mode_command).grid(row=2, column=1, sticky="w", pady=(0, 5)) + ttk.Label(controls_frame, text="Mode: ").grid(row=1, column=0, pady=(5, 0)) + ttk.Radiobutton(controls_frame, text="Player", variable=mode_var, value=FullAutoMode.Player.value, command=mode_command).grid(row=1, column=1, sticky="w") + ttk.Radiobutton(controls_frame, text="Recorder", variable=mode_var, value=FullAutoMode.Recorder.value, command=mode_command).grid(row=2, column=1, sticky="w", pady=(0, 5)) - tk.Button(controls_frame, text="Select fishy file", command=select_file).grid(row=3, column=0, columnspan=2, pady=(5, 0)) - tk.Label(controls_frame, textvariable=file_name_label).grid(row=4, column=0, columnspan=2, pady=(0, 5)) + ttk.Button(controls_frame, text="Select fishy file", command=select_file).grid(row=3, column=0, columnspan=2, pady=(5, 0)) + ttk.Label(controls_frame, textvariable=file_name_label).grid(row=4, column=0, columnspan=2, pady=(0, 5)) - tk.Label(controls_frame, text="Use semi-fisher config for rest").grid(row=5, column=0, columnspan=2, pady=(15, 5)) + ttk.Label(controls_frame, text="Use semi-fisher config for rest").grid(row=5, column=0, columnspan=2, pady=(15, 5)) controls_frame.pack(padx=(5, 5), pady=(5, 5)) top.start() From 55c867f7902b4e6703803082db10d6878156f532 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Thu, 13 May 2021 13:47:43 +0530 Subject: [PATCH 12/19] changed from yes/no to overwrite/save as/cancel option in recorder --- fishy/engine/fullautofisher/mode/recorder.py | 43 ++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index 4da303e..e83cc61 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -2,10 +2,14 @@ import logging import os import pickle import time +from tkinter import ttk from tkinter.messagebox import askyesno import typing from tkinter.filedialog import asksaveasfile +from fishy.helper.helper import empty_function + +from fishy.helper.popup import PopUp from playsound import playsound from fishy import helper @@ -56,13 +60,46 @@ class Recorder(IMode): hk.stop() self._ask_to_save() + def _open_save_popup(self): + top = PopUp(empty_function, self.engine.get_gui()._root, background=self.engine.get_gui()._root["background"]) + controls_frame = ttk.Frame(top) + top.title("Save Recording?") + + button = [-1] + + def button_pressed(_button): + button[0] = _button + top.quit_top() + + ttk.Label(controls_frame, text="Do you want to save the recording?").grid(row=0, column=0, columnspan=3) + + _overwrite = "normal" if config.get("full_auto_rec_file") else "disable" + ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0) + ttk.Button(controls_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1) + ttk.Button(controls_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2) + + controls_frame.pack(padx=(5, 5), pady=(5, 5)) + top.start() + + return button[0] + def _ask_to_save(self): def func(): _file = None files = [('Fishy File', '*.fishy')] - while not _file and askyesno("Save Recording?", "Would you like to save the recording?"): - _file = asksaveasfile(mode='wb', filetypes=files, defaultextension=files) - return _file + + while True: + button = self._open_save_popup() + if button == 0 and config.get("full_auto_rec_file"): + return open(config.get("full_auto_rec_file"), 'wb') + + if button == 1: + _file = asksaveasfile(mode='wb', filetypes=files, defaultextension=files) + if _file: + return _file + + if button == 2: + return None file: typing.BinaryIO = self.engine.get_gui().call_in_thread(func, block=True) if not file: From c21a6f06f21c700754f9f4057c788a6a9cdaaa70 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Thu, 13 May 2021 14:23:54 +0530 Subject: [PATCH 13/19] resume from closest point instead of start --- fishy/engine/fullautofisher/mode/player.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fishy/engine/fullautofisher/mode/player.py b/fishy/engine/fullautofisher/mode/player.py index 8d22d9f..fa25a63 100644 --- a/fishy/engine/fullautofisher/mode/player.py +++ b/fishy/engine/fullautofisher/mode/player.py @@ -1,4 +1,5 @@ import logging +import math import pickle from pprint import pprint @@ -55,6 +56,14 @@ class Player(IMode): log_raise("data not found, can't start") logging.info("starting player") + self.i = self._closest_point() + + def _closest_point(self): + current = self.engine.get_coods() + distances = [(i, math.sqrt((target[0] - current[0]) ** 2 + (target[1] - current[1]) ** 2)) + for i, (command, target) in enumerate(self.timeline) if command == "move_to"] + return min(distances, key=lambda d: d[1])[0] + def _loop(self): action = self.timeline[self.i] From 73a377d919147971217a044ef96163b9d1547cec Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Thu, 13 May 2021 14:51:59 +0530 Subject: [PATCH 14/19] added config for edit mode --- fishy/engine/fullautofisher/mode/recorder.py | 3 ++- fishy/gui/config_top.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index e83cc61..da8daf2 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -2,6 +2,7 @@ import logging import os import pickle import time +import tkinter as tk from tkinter import ttk from tkinter.messagebox import askyesno import typing @@ -73,7 +74,7 @@ class Recorder(IMode): ttk.Label(controls_frame, text="Do you want to save the recording?").grid(row=0, column=0, columnspan=3) - _overwrite = "normal" if config.get("full_auto_rec_file") else "disable" + _overwrite = tk.NORMAL if config.get("full_auto_rec_file") else tk.DISABLED ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0) ttk.Button(controls_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1) ttk.Button(controls_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2) diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index d6e3cac..eb4b38d 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -46,9 +46,14 @@ def start_fullfisher_config(gui: 'GUI'): def mode_command(): config.set("full_auto_mode", mode_var.get()) + edit_cb['state'] = "normal" if config.get("full_auto_mode", 0) == FullAutoMode.Recorder.value else "disable" + + def edit_mode_changed(): + config.set("edit_recorder_mode", edit_var.get()) file_name_label = tk.StringVar(value=file_name()) mode_var = tk.IntVar(value=config.get("full_auto_mode", 0)) + edit_var = tk.IntVar(value=config.get("edit_recorder_mode", 0)) ttk.Button(controls_frame, text="Calibrate", command=start_calibrate).grid(row=0, column=0, columnspan=2, pady=(5, 5)) @@ -56,10 +61,15 @@ def start_fullfisher_config(gui: 'GUI'): ttk.Radiobutton(controls_frame, text="Player", variable=mode_var, value=FullAutoMode.Player.value, command=mode_command).grid(row=1, column=1, sticky="w") ttk.Radiobutton(controls_frame, text="Recorder", variable=mode_var, value=FullAutoMode.Recorder.value, command=mode_command).grid(row=2, column=1, sticky="w", pady=(0, 5)) - ttk.Button(controls_frame, text="Select fishy file", command=select_file).grid(row=3, column=0, columnspan=2, pady=(5, 0)) - ttk.Label(controls_frame, textvariable=file_name_label).grid(row=4, column=0, columnspan=2, pady=(0, 5)) + ttk.Label(controls_frame, text="Edit Mode: ").grid(row=3, column=0, pady=(5, 5)) + edit_state = tk.NORMAL if config.get("full_auto_mode", 0) == FullAutoMode.Recorder.value else tk.DISABLED + edit_cb = ttk.Checkbutton(controls_frame, variable=edit_var, state=edit_state, command=edit_mode_changed) + edit_cb.grid(row=3, column=1, pady=(5, 5)) - ttk.Label(controls_frame, text="Use semi-fisher config for rest").grid(row=5, column=0, columnspan=2, pady=(15, 5)) + ttk.Button(controls_frame, text="Select fishy file", command=select_file).grid(row=4, column=0, columnspan=2, pady=(5, 0)) + ttk.Label(controls_frame, textvariable=file_name_label).grid(row=5, column=0, columnspan=2, pady=(0, 5)) + + ttk.Label(controls_frame, text="Use semi-fisher config for rest").grid(row=6, column=0, columnspan=2, pady=(15, 5)) controls_frame.pack(padx=(5, 5), pady=(5, 5)) top.start() From fa02a0895bd0e1c1a36a703e737f4c1b8c85f3fd Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Thu, 13 May 2021 16:54:49 +0530 Subject: [PATCH 15/19] show the name of the recording while saving --- fishy/engine/fullautofisher/mode/recorder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index da8daf2..30ef2db 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -72,10 +72,11 @@ class Recorder(IMode): button[0] = _button top.quit_top() - ttk.Label(controls_frame, text="Do you want to save the recording?").grid(row=0, column=0, columnspan=3) + selected_text = f"\n\nSelected: {os.path.basename(config.get('full_auto_rec_file'))}" if config.get('full_auto_rec_file') else "" + ttk.Label(controls_frame, text=f"Do you want to save the recording?{selected_text}").grid(row=0, column=0, columnspan=3, pady=(0, 5)) _overwrite = tk.NORMAL if config.get("full_auto_rec_file") else tk.DISABLED - ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0) + ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0, pady=(5, 0)) ttk.Button(controls_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1) ttk.Button(controls_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2) From 41232cc723c2429dae0802fbd10e73d62b97b10c Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Thu, 13 May 2021 17:34:50 +0530 Subject: [PATCH 16/19] path editing mode done --- fishy/engine/fullautofisher/mode/player.py | 24 ++++++++++++-------- fishy/engine/fullautofisher/mode/recorder.py | 22 +++++++++++++++++- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/player.py b/fishy/engine/fullautofisher/mode/player.py index fa25a63..bb1d827 100644 --- a/fishy/engine/fullautofisher/mode/player.py +++ b/fishy/engine/fullautofisher/mode/player.py @@ -17,7 +17,7 @@ from fishy.helper import helper from fishy.helper.config import config -def _get_rec_file(): +def get_rec_file(): file = config.get("full_auto_rec_file") if not file: @@ -27,13 +27,23 @@ def _get_rec_file(): file = open(file, 'rb') data = pickle.load(file) file.close() - pprint(data) if "full_auto_path" not in data: logging.error("invalid file") return None return data["full_auto_path"] +def find_nearest(timeline, current): + """ + :param timeline: recording timeline + :param current: current coord + :return: Tuple[index, distance, target_coord] + """ + distances = [(i, math.sqrt((target[0] - current[0]) ** 2 + (target[1] - current[1]) ** 2), target) + for i, (command, target) in enumerate(timeline) if command == "move_to"] + return min(distances, key=lambda d: d[1]) + + class Player(IMode): def __init__(self, engine: 'FullAuto'): self.recording = False @@ -51,18 +61,12 @@ class Player(IMode): logging.info("player stopped") def _init(self): - self.timeline = _get_rec_file() + self.timeline = get_rec_file() if not self.timeline: log_raise("data not found, can't start") logging.info("starting player") - self.i = self._closest_point() - - def _closest_point(self): - current = self.engine.get_coods() - distances = [(i, math.sqrt((target[0] - current[0]) ** 2 + (target[1] - current[1]) ** 2)) - for i, (command, target) in enumerate(self.timeline) if command == "move_to"] - return min(distances, key=lambda d: d[1])[0] + self.i = find_nearest(self.timeline, self.engine.get_coods())[0] def _loop(self): action = self.timeline[self.i] diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index 30ef2db..06c9b32 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -5,15 +5,18 @@ import time import tkinter as tk from tkinter import ttk from tkinter.messagebox import askyesno +from typing import List, Optional import typing from tkinter.filedialog import asksaveasfile +from fishy.engine.fullautofisher.mode import player +from fishy.helper import helper + from fishy.helper.helper import empty_function from fishy.helper.popup import PopUp from playsound import playsound -from fishy import helper from fishy.helper.config import config if typing.TYPE_CHECKING: @@ -39,6 +42,14 @@ class Recorder(IMode): logging.info("check_fish") def run(self): + old_timeline: Optional[List] = None + start_from = None + if config.get("edit_recorder_mode"): + logging.info("moving to nearest coord in recording") + old_timeline = player.get_rec_file() + start_from = player.find_nearest(old_timeline, self.engine.get_coods()) + self.engine.move_to(start_from[2]) + logging.info("starting, press LMB to mark hole") hk = HotKey() hk.start_process(self._mark_hole) @@ -59,6 +70,15 @@ class Recorder(IMode): logging.warning("Took too much time to record") hk.stop() + + if config.get("edit_recorder_mode"): + logging.info("moving to nearest coord in recording") + end = player.find_nearest(old_timeline, self.engine.get_coods()) + self.engine.move_to(end[2]) + part1 = old_timeline[:start_from[0]] + part2 = old_timeline[end[0]:] + self.timeline = part1 + self.timeline + part2 + self._ask_to_save() def _open_save_popup(self): From db3def3948686ad48b8e8b53d36159e7463d58f2 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sat, 15 May 2021 17:50:38 +0530 Subject: [PATCH 17/19] pause Instead of shutting off engine when qr doesn't get read --- fishy/engine/fullautofisher/engine.py | 38 +++++++++++-------- .../engine/fullautofisher/mode/calibrator.py | 8 ++-- fishy/engine/fullautofisher/mode/player.py | 23 +++++++++-- fishy/engine/fullautofisher/mode/recorder.py | 31 +++++++++++---- fishy/engine/fullautofisher/qr_detection.py | 2 +- fishy/engine/fullautofisher/test.py | 5 ++- 6 files changed, 73 insertions(+), 34 deletions(-) diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 01bcf3c..4889bdf 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -121,20 +121,21 @@ class FullAuto(IEngine): self.start = False Thread(target=func).start() - def get_coods(self): + def get_coords(self): + """ + There is chance that this function give None instead of a QR. + Need to handle manually + todo find a better way of handling None: switch from start bool to state which knows + todo its waiting for qr which doesn't block the engine when commanded to close + """ img = self.window.processed_image(func=image_pre_process) return get_values_from_image(img) - def move_to(self, target): - if target is None: - logging.error("set target first") - return + def move_to(self, target) -> bool: + current = self.get_coords() + if not current: + return False - if not self.calibrator.all_calibrated(): - logging.error("you need to calibrate first") - return - - current = self.get_coods() print(f"Moving from {(current[0], current[1])} to {target}") move_vec = target[0] - current[0], target[1] - current[1] @@ -142,12 +143,13 @@ class FullAuto(IEngine): print(f"distance: {dist}") if dist < 5e-05: print("distance very small skipping") - return + return True target_angle = math.degrees(math.atan2(-move_vec[1], move_vec[0])) + 90 from_angle = current[2] - self.rotate_to(target_angle, from_angle) + if not self.rotate_to(target_angle, from_angle): + return False walking_time = dist / self.calibrator.move_factor print(f"walking for {walking_time}") @@ -155,10 +157,14 @@ class FullAuto(IEngine): time.sleep(walking_time) kb.release('w') print("done") + return True - def rotate_to(self, target_angle, from_angle=None): + def rotate_to(self, target_angle, from_angle=None) -> bool: if from_angle is None: - _, _, from_angle = self.get_coods() + coords = self.get_coords() + if not coords: + return False + _, _, from_angle = coords if target_angle < 0: target_angle = 360 + target_angle @@ -179,7 +185,9 @@ class FullAuto(IEngine): mse.move(sign(rotate_times) * FullAuto.rotate_by * -1, 0) time.sleep(0.05) - def look_for_hole(self): + return True + + def look_for_hole(self) -> bool: self._hole_found_flag = False if FishingMode.CurrentMode == fishing_mode.State.LOOKING: diff --git a/fishy/engine/fullautofisher/mode/calibrator.py b/fishy/engine/fullautofisher/mode/calibrator.py index 6d499fa..c1805f9 100644 --- a/fishy/engine/fullautofisher/mode/calibrator.py +++ b/fishy/engine/fullautofisher/mode/calibrator.py @@ -75,7 +75,7 @@ class Calibrator(IMode): def _walk_calibrate(self): walking_time = 3 - coods = self.engine.get_coods() + coods = self.engine.get_coords() if coods is None: return @@ -86,7 +86,7 @@ class Calibrator(IMode): kb.release('w') time.sleep(0.5) - coods = self.engine.get_coods() + coods = self.engine.get_coords() if coods is None: return x2, y2, rot2 = coods @@ -100,7 +100,7 @@ class Calibrator(IMode): rotate_times = 50 - coods = self.engine.get_coods() + coods = self.engine.get_coords() if coods is None: return _, _, rot2 = coods @@ -109,7 +109,7 @@ class Calibrator(IMode): mse.move(FullAuto.rotate_by, 0) time.sleep(0.05) - coods = self.engine.get_coods() + coods = self.engine.get_coords() if coods is None: return x3, y3, rot3 = coods diff --git a/fishy/engine/fullautofisher/mode/player.py b/fishy/engine/fullautofisher/mode/player.py index bb1d827..0c21777 100644 --- a/fishy/engine/fullautofisher/mode/player.py +++ b/fishy/engine/fullautofisher/mode/player.py @@ -1,6 +1,7 @@ import logging import math import pickle +import time from pprint import pprint import typing @@ -58,6 +59,7 @@ class Player(IMode): self._init() while self.engine.start: self._loop() + time.sleep(0.1) logging.info("player stopped") def _init(self): @@ -66,16 +68,26 @@ class Player(IMode): log_raise("data not found, can't start") logging.info("starting player") - self.i = find_nearest(self.timeline, self.engine.get_coods())[0] + coords = self.engine.get_coords() + if not coords: + log_raise("QR not found") + + self.i = find_nearest(self.timeline, coords)[0] def _loop(self): action = self.timeline[self.i] if action[0] == "move_to": - self.engine.move_to(action[1]) + if not self.engine.move_to(action[1]): + return elif action[0] == "check_fish": - self.engine.move_to(action[1]) - self.engine.rotate_to(action[1][2]) + if not self.engine.move_to(action[1]): + return + + if not self.engine.rotate_to(action[1][2]): + return + + # todo swap the order of below two lines fishing_event.subscribe() fishing_mode.subscribers.append(self._hole_complete_callback) # scan for fish hole @@ -92,6 +104,9 @@ class Player(IMode): fishing_event.unsubscribe() fishing_mode.subscribers.remove(self._hole_complete_callback) + self.next() + + def next(self): self.i += 1 if self.forward else -1 if self.i >= len(self.timeline): self.forward = False diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index 06c9b32..8acdbf4 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -12,7 +12,7 @@ from tkinter.filedialog import asksaveasfile from fishy.engine.fullautofisher.mode import player from fishy.helper import helper -from fishy.helper.helper import empty_function +from fishy.helper.helper import empty_function, log_raise from fishy.helper.popup import PopUp from playsound import playsound @@ -36,7 +36,10 @@ class Recorder(IMode): self.timeline = [] def _mark_hole(self): - coods = self.engine.get_coods() + coods = self.engine.get_coords() + if not coods: + logging.warning("QR not found, couldn't record hole") + return self.timeline.append(("check_fish", coods)) playsound(helper.manifest_file("beep.wav"), False) logging.info("check_fish") @@ -47,8 +50,12 @@ class Recorder(IMode): if config.get("edit_recorder_mode"): logging.info("moving to nearest coord in recording") old_timeline = player.get_rec_file() - start_from = player.find_nearest(old_timeline, self.engine.get_coods()) - self.engine.move_to(start_from[2]) + coords = self.engine.get_coords() + if not coords: + log_raise("QR not found") + start_from = player.find_nearest(old_timeline, coords) + if not self.engine.move_to(start_from[2]): + log_raise("QR not found") logging.info("starting, press LMB to mark hole") hk = HotKey() @@ -58,9 +65,11 @@ class Recorder(IMode): while self.engine.start: start_time = time.time() - coods = None - while not coods: - coods = self.engine.get_coods() + coods = self.engine.get_coords() + if not coods: + time.sleep(0.1) + continue + self.timeline.append(("move_to", (coods[0], coods[1]))) time_took = time.time() - start_time @@ -73,7 +82,13 @@ class Recorder(IMode): if config.get("edit_recorder_mode"): logging.info("moving to nearest coord in recording") - end = player.find_nearest(old_timeline, self.engine.get_coods()) + + # todo allow the user the chance to wait for qr + coords = self.engine.get_coords() + if not coords: + log_raise("QR not found") + + end = player.find_nearest(old_timeline, coords) self.engine.move_to(end[2]) part1 = old_timeline[:start_from[0]] part2 = old_timeline[end[0]:] diff --git a/fishy/engine/fullautofisher/qr_detection.py b/fishy/engine/fullautofisher/qr_detection.py index ca6838b..2c969e4 100644 --- a/fishy/engine/fullautofisher/qr_detection.py +++ b/fishy/engine/fullautofisher/qr_detection.py @@ -42,7 +42,7 @@ def get_values_from_image(img): vals = qr.data.decode('utf-8').split(",") return float(vals[0]), float(vals[1]), float(vals[2]) - logging.error("FishyQR not found, try restarting the engine") + logging.error("FishyQR not found") return None except Exception: logging.error("Couldn't read coods, make sure 'crop' calibration is correct") diff --git a/fishy/engine/fullautofisher/test.py b/fishy/engine/fullautofisher/test.py index ea50efa..07c48c9 100644 --- a/fishy/engine/fullautofisher/test.py +++ b/fishy/engine/fullautofisher/test.py @@ -8,11 +8,12 @@ class Test: self.engine = engine self.target = None + # noinspection PyProtectedMember def print_coods(self): - logging.info(self.engine.get_coods()) + logging.info(self.engine.get_coords()) def set_target(self): - self.target = self.engine.get_coods() + self.target = self.engine.get_coords() logging.info(f"target_coods are {self.target}") def move_to_target(self): From 810c0276a133b30cc427117805deba93bc994312 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sat, 15 May 2021 17:52:04 +0530 Subject: [PATCH 18/19] possible fix for ingnoring fishing holes --- fishy/engine/fullautofisher/mode/player.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fishy/engine/fullautofisher/mode/player.py b/fishy/engine/fullautofisher/mode/player.py index 0c21777..a573788 100644 --- a/fishy/engine/fullautofisher/mode/player.py +++ b/fishy/engine/fullautofisher/mode/player.py @@ -87,9 +87,8 @@ class Player(IMode): if not self.engine.rotate_to(action[1][2]): return - # todo swap the order of below two lines - fishing_event.subscribe() fishing_mode.subscribers.append(self._hole_complete_callback) + fishing_event.subscribe() # scan for fish hole logging.info("scanning") # if found start fishing and wait for hole to complete @@ -101,8 +100,8 @@ class Player(IMode): else: logging.info("no hole found") # continue when hole completes - fishing_event.unsubscribe() fishing_mode.subscribers.remove(self._hole_complete_callback) + fishing_event.unsubscribe() self.next() From 901c8d6ea80ee3b709d60ddd553cc50733556d68 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Sat, 15 May 2021 18:24:27 +0530 Subject: [PATCH 19/19] full auto doesn't stop when waiting for game window to be active --- fishy/engine/fullautofisher/engine.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 4889bdf..8cc8e47 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -75,9 +75,10 @@ class FullAuto(IEngine): if not is_eso_active(): logging.info("Waiting for eso window to be active...") - wait_until(is_eso_active) - logging.info("starting in 2 secs...") - time.sleep(2) + wait_until(lambda: is_eso_active() or not self.start) + if self.start: + logging.info("starting in 2 secs...") + time.sleep(2) # noinspection PyBroadException try: