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')