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/engine/fullautofisher/controls.py b/fishy/engine/fullautofisher/controls.py index 8949466..e18db67 100644 --- a/fishy/engine/fullautofisher/controls.py +++ b/fishy/engine/fullautofisher/controls.py @@ -1,26 +1,16 @@ import logging -from fishy.engine.fullautofisher.engine import FullAuto, State +from pynput.keyboard import Key + from fishy.helper import hotkey -from fishy.helper.hotkey import Key -def get_controls(engine: FullAuto): - from fishy.engine.fullautofisher.player import Player - from fishy.engine.fullautofisher.recorder import Recorder +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 @@ -44,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 c8c47e3..8cc8e47 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -1,8 +1,8 @@ +import math import logging import math import time import traceback -from enum import Enum from threading import Thread import cv2 @@ -12,11 +12,17 @@ from fishy.constants import fishyqr, lam2, libgps from fishy.engine import SemiFisherEngine from fishy.engine.common.IEngine import IEngine from fishy.engine.common.window import WindowClient +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.semifisher import fishing_event, fishing_mode from fishy.engine.semifisher.fishing_mode import FishingMode from fishy.helper import helper, hotkey +from fishy.helper.config import config +from fishy.helper.helper import log_raise, wait_until, is_eso_active from fishy.helper.helper import sign mse = mouse.Controller() @@ -32,21 +38,10 @@ 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 import controls - from fishy.engine.fullautofisher.calibrator import Calibrator - from fishy.engine.fullautofisher.controls import Controls from fishy.engine.fullautofisher.test import Test super().__init__(gui_ref) @@ -56,9 +51,10 @@ 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 + self.mode = None + def run(self): addons_req = [libgps, lam2, fishyqr] @@ -66,57 +62,81 @@ 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 = 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) + + if not is_eso_active(): + logging.info("Waiting for eso window to be active...") + 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: + 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") + if not self.calibrator.all_calibrated(): + log_raise("you need to calibrate first") self.fisher.toggle_start() fishing_event.unsubscribe() + if self.show_crop: + self.start_show() + self.stop_on_inactive() - self.controls.initialize() - 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.controls.unassign_keys() self.window.show(False) logging.info("Quitting") self.window.destory() self.fisher.toggle_start() - def get_coods(self): + 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 stop_on_inactive(self): + def func(): + wait_until(lambda: not is_eso_active()) + self.start = False + Thread(target=func).start() + + 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_callibrated(): - logging.error("you need to callibrate 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] @@ -124,12 +144,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}") @@ -137,10 +158,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 @@ -161,7 +186,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: @@ -194,10 +221,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/calibrator.py b/fishy/engine/fullautofisher/mode/calibrator.py similarity index 73% rename from fishy/engine/fullautofisher/calibrator.py rename to fishy/engine/fullautofisher/mode/calibrator.py index 87a0a5a..c1805f9 100644 --- a/fishy/engine/fullautofisher/calibrator.py +++ b/fishy/engine/fullautofisher/mode/calibrator.py @@ -1,12 +1,17 @@ import logging import math import time +import typing import cv2 import numpy as np + +if typing.TYPE_CHECKING: + from fishy.engine.fullautofisher.engine import FullAuto + +from fishy.engine.fullautofisher.mode.imode import IMode from pynput import keyboard, mouse -from fishy.engine.fullautofisher.engine import FullAuto from fishy.helper.config import config mse = mouse.Controller() @@ -43,8 +48,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 @@ -58,8 +63,11 @@ class Calibrator: # 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 @@ -67,7 +75,7 @@ class Calibrator: def _walk_calibrate(self): walking_time = 3 - coods = self.engine.get_coods() + coods = self.engine.get_coords() if coods is None: return @@ -78,19 +86,21 @@ class Calibrator: 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 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() + coods = self.engine.get_coords() if coods is None: return _, _, rot2 = coods @@ -99,7 +109,7 @@ class Calibrator: 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 @@ -109,8 +119,10 @@ 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/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..a573788 --- /dev/null +++ b/fishy/engine/fullautofisher/mode/player.py @@ -0,0 +1,119 @@ +import logging +import math +import pickle +import time +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() + 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 + 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() + time.sleep(0.1) + 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") + + 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": + if not self.engine.move_to(action[1]): + return + elif action[0] == "check_fish": + if not self.engine.move_to(action[1]): + return + + if not self.engine.rotate_to(action[1][2]): + return + + 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 + 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_mode.subscribers.remove(self._hole_complete_callback) + fishing_event.unsubscribe() + + self.next() + + def next(self): + 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/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py new file mode 100644 index 0000000..8acdbf4 --- /dev/null +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -0,0 +1,149 @@ +import logging +import os +import pickle +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, log_raise + +from fishy.helper.popup import PopUp +from playsound import playsound + +from fishy.helper.config import config + +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(IMode): + recording_fps = 1 + mark_hole_key = Key.F8 + + def __init__(self, engine: 'FullAuto'): + self.recording = False + self.engine = engine + self.timeline = [] + + def _mark_hole(self): + 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") + + 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() + 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() + hk.start_process(self._mark_hole) + + self.timeline = [] + + while self.engine.start: + start_time = time.time() + 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 + if time_took <= Recorder.recording_fps: + time.sleep(Recorder.recording_fps - time_took) + else: + logging.warning("Took too much time to record") + + hk.stop() + + if config.get("edit_recorder_mode"): + logging.info("moving to nearest coord in recording") + + # 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]:] + self.timeline = part1 + self.timeline + part2 + + 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() + + 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, 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) + + 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 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: + return + + data = {"full_auto_path": self.timeline} + 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() diff --git a/fishy/engine/fullautofisher/player.py b/fishy/engine/fullautofisher/player.py deleted file mode 100644 index 2322b9e..0000000 --- a/fishy/engine/fullautofisher/player.py +++ /dev/null @@ -1,93 +0,0 @@ -import logging -import pickle -from pprint import pprint - -from fishy.engine.fullautofisher.engine import FullAuto, State -from fishy.engine.semifisher import fishing_event, fishing_mode -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/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/recorder.py b/fishy/engine/fullautofisher/recorder.py deleted file mode 100644 index b4b614b..0000000 --- a/fishy/engine/fullautofisher/recorder.py +++ /dev/null @@ -1,70 +0,0 @@ -import logging -import pickle -import time -from pprint import pprint -from tkinter.filedialog import asksaveasfile - -from fishy.engine.fullautofisher.engine import FullAuto, State -from fishy.helper.hotkey import Key -from fishy.helper.hotkey_process import HotKey - - -class Recorder: - recording_fps = 1 - mark_hole_key = Key.F8 - - def __init__(self, engine: FullAuto): - self.recording = False - self.engine = engine - self.timeline = [] - - def _mark_hole(self): - coods = self.engine.get_coods() - 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 - logging.info("starting, press f8 to mark hole") - hk = HotKey() - hk.start_process(self._mark_hole) - - self.timeline = [] - - while self.recording: - start_time = time.time() - coods = None - while not coods: - coods = self.engine.get_coods() - self.timeline.append(("move_to", (coods[0], coods[1]))) - - time_took = time.time() - start_time - if time_took <= Recorder.recording_fps: - time.sleep(Recorder.recording_fps - time_took) - else: - logging.warning("Took too much time to record") - - hk.stop() - - def func(): - _file = None - files = [('Fishy File', '*.fishy')] - while not _file: - _file = asksaveasfile(mode='wb', filetypes=files, defaultextension=files) - - return _file - - file = self.engine.get_gui().call_in_thread(func, block=True) - data = {"full_auto_path": self.timeline} - pprint(data) - pickle.dump(data, file) - file.close() - FullAuto.state = State.NONE 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): diff --git a/fishy/engine/semifisher/fishing_event.py b/fishy/engine/semifisher/fishing_event.py index c81486e..de8b839 100644 --- a/fishy/engine/semifisher/fishing_event.py +++ b/fishy/engine/semifisher/fishing_event.py @@ -9,13 +9,13 @@ import time import keyboard from playsound import playsound -from win32gui import GetForegroundWindow, GetWindowText from fishy import web from fishy.engine.semifisher import fishing_mode from fishy.engine.semifisher.fishing_mode import FishingMode, State from fishy.helper import helper from fishy.helper.config import config +from fishy.helper.helper import is_eso_active class FishEvent: @@ -44,7 +44,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/gui/config_top.py b/fishy/gui/config_top.py index 886ec41..eb4b38d 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -5,6 +5,10 @@ import tkinter.ttk as ttk 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 from fishy.helper import helper from fishy.helper.config import config @@ -35,10 +39,37 @@ 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()) + 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()) - ttk.Label(controls_frame, textvariable=file_name_label).grid(row=0, column=0) - ttk.Button(controls_frame, text="Select fishy file", command=select_file).grid(row=0, column=1) - ttk.Label(controls_frame, text="Use semi-fisher config for rest").grid(row=2, column=0, columnspan=2) + 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)) + + 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)) + + 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.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() @@ -106,3 +137,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 dc6ed5c..6025f84 100644 --- a/fishy/gui/gui.py +++ b/fishy/gui/gui.py @@ -7,7 +7,7 @@ from typing import Any, Callable, Dict, Optional 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 fishy.web import web @@ -19,7 +19,7 @@ from .log_config import GUIStreamHandler 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 fa9841e..773c32b 100644 --- a/fishy/helper/config.py +++ b/fishy/helper/config.py @@ -106,7 +106,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): @@ -116,6 +116,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() @@ -134,4 +137,6 @@ class config: @staticmethod def save_config(): + if config._instance is None: + return config._instance.save_config() diff --git a/fishy/helper/helper.py b/fishy/helper/helper.py index 5536976..35ffb16 100644 --- a/fishy/helper/helper.py +++ b/fishy/helper/helper.py @@ -1,3 +1,4 @@ +import ctypes import logging import os import shutil @@ -17,6 +18,7 @@ import winshell from playsound import playsound from win32com.client import Dispatch from win32comext.shell import shell, shellcon +from win32gui import GetForegroundWindow, GetWindowText import fishy from fishy import web @@ -222,8 +224,36 @@ 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() + + +def is_eso_active(): + return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online" + + +# 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')