From faf2c62e9c63b07d0b79ecc876f7b276b4626f5e Mon Sep 17 00:00:00 2001 From: "DESKTOP-JVKHS7I\\Adam" Date: Sat, 18 Apr 2020 17:02:14 +0530 Subject: [PATCH] added help menu, removed pause and stop system, tweaked window, fishing_mode, fishing_event to work with new design, config now saves on other thread --- .gitignore | 3 +- fishy/__main__.py | 149 +++++++++++++++++---------------- fishy/systems/__init__.py | 1 - fishy/systems/config.json | 1 - fishy/systems/config.py | 13 ++- fishy/systems/controls.py | 89 -------------------- fishy/systems/fishing_event.py | 5 +- fishy/systems/fishing_mode.py | 4 +- fishy/systems/globals.py | 2 - fishy/systems/gui.py | 80 ++++++++++-------- fishy/systems/helper.py | 8 ++ fishy/systems/window.py | 20 ++--- 12 files changed, 155 insertions(+), 220 deletions(-) delete mode 100644 fishy/systems/config.json delete mode 100644 fishy/systems/controls.py diff --git a/.gitignore b/.gitignore index 1feb29d..21d344b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ template_folder/ checkpoint venv/ *.pickle -fishy.egg-info/ \ No newline at end of file +fishy.egg-info/ +config.json \ No newline at end of file diff --git a/fishy/__main__.py b/fishy/__main__.py index d3cae50..2d69cb9 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -1,97 +1,98 @@ +import time +from threading import Thread + import cv2 -from docopt import docopt -from pynput.keyboard import Listener +import pywintypes from fishy.systems import * import logging from fishy.systems.config import Config -from fishy.systems.gui import GUI, GUIStreamHandler, GUICallback - -""" -Start reading from `init.py` -""" +from fishy.systems.gui import GUI, GUIStreamHandler, GUIEvent, GUIFunction -def on_release(key): - """ - Reads input, finds out the resultant action and performs it +class Fishy: + def __init__(self, gui_ref, gui_event_buffer): + self.gui_events = gui_event_buffer + self.start = False + self.fishPixWindow = None + self.fishy_thread = None + self.gui = gui_ref - :param key: key pressed - """ + def start_fishing(self, ip: str, action_key: str, borderless: bool, collect_r: bool): + """ + Starts the fishing + code explained in comments in detail + """ - c = Control.find(key) - if c is None: - return + if ip != "": + net.initialize(ip) - if c[0] == Control.Keywords.StartPause: - - if not G.pause: - logging.info("PAUSED") - G.pause = True + # initialize widow + try: + Window.Init(borderless) + except pywintypes.error: + logging.info("Game window not found") + self.start = False return - if PixelLoc.config(): - logging.info("STARTED") - G.pause = False - else: - logging.info("addon properly not installed, if it is installed try restarting the game.") + # initializes fishing modes and their callbacks + FishingMode("hook", 0, HookEvent(collect_r)) + FishingMode("stick", 1, StickEvent()) + FishingMode("look", 2, LookEvent()) + FishingMode("idle", 3, IdleEvent(ip != "")) - elif c[0] == Control.Keywords.Debug: - G.debug = not G.debug + logging.info("Starting the bot engine, look at the fishing hole to start fishing") - elif c[0] == Control.Keywords.Stop: - G.stop = True - - elif c[0] == Control.Keywords.SwitchMode: - Control.nextState() - logging.info(Control.getControlHelp()) - - -def hsv2rgb(img): - return cv2.cvtColor(img, cv2.COLOR_HSV2RGB) - - -def startFishing(ip: str, action_key: str, borderless: bool): - """ - Starts the fishing - code explained in comments in detail - """ - - if ip != "": - net.initialize(ip) - - # initializes fishing modes and their callbacks - FishingMode("hook", 0, HookEvent()) - FishingMode("stick", 1, StickEvent()) - FishingMode("look", 2, LookEvent()) - FishingMode("idle", 3, IdleEvent(ip != "")) - - logging.info("Starting the bot engine, look at the fishing hole to start fishing") - - fishPixWindow = Window(color=cv2.COLOR_RGB2HSV) - - # initialize widow - Window.Init() - with Listener(on_release=on_release): - while not G.stop: - # record the time to calculate time taken to execute one loop + self.fishPixWindow = Window(color=cv2.COLOR_RGB2HSV) + # check for game window and stuff + self.gui.call(GUIFunction.STARTED, (True,)) + while self.start: # Services to be ran in the start of the main loop Window.Loop() # get the PixelLoc and find the color values, to give it to `FishingMode.Loop` - fishPixWindow.crop = PixelLoc.val - hueValue = fishPixWindow.getCapture()[0][0][0] - FishingMode.Loop(hueValue, G.pause) - - # if debug is on, show the color on the PixelLoc in a window and print the hue values of it - if G.debug: - fishPixWindow.show("pixloc", resize=200, func=hsv2rgb) - logging.debug(str(FishingMode.CurrentMode.label) + ":" + str(fishPixWindow.getCapture()[0][0])) + self.fishPixWindow.crop = PixelLoc.val + hueValue = self.fishPixWindow.getCapture()[0][0][0] + FishingMode.Loop(hueValue) # Services to be ran in the end of the main loop Window.LoopEnd() + logging.info("Fishing engine stopped") + self.gui.call(GUIFunction.STARTED, (False,)) + + def start_event_handler(self): + while True: + while len(self.gui_events) > 0: + event = self.gui_events.pop(0) + + if event[0] == GUIEvent.START_BUTTON: + self.start = not self.start + if self.start: + self.fishy_thread = Thread(target=self.start_fishing, args=(*event[1],)) + self.fishy_thread.start() + elif event[0] == GUIEvent.CHECK_PIXELVAL: + if self.start: + self.show_pixel_vals() + else: + logging.debug("Start the engine first before running this command") + elif event[0] == GUIEvent.QUIT: + self.start = False + return + + def show_pixel_vals(self): + def show(): + freq = 0.5 + t = 0 + while t < 10.0: + t += freq + logging.debug(str(FishingMode.CurrentMode.label) + ":" + str(self.fishPixWindow.getCapture()[0][0])) + time.sleep(freq) + + logging.debug("Will display pixel values for 10 seconds") + time.sleep(5) + Thread(target=show, args=()).start() def main(): @@ -99,12 +100,14 @@ def main(): rootLogger = logging.getLogger('') rootLogger.setLevel(logging.DEBUG) - gui = GUI(Config(), lambda a, b: events_buffer.append((a, b))) + gui = GUI(Config(), lambda a, b=None: events_buffer.append((a, b))) gui.start() new_console = GUIStreamHandler(gui) rootLogger.addHandler(new_console) - G.arguments = docopt(__doc__) + + fishy = Fishy(gui, events_buffer) + fishy.start_event_handler() if __name__ == "__main__": diff --git a/fishy/systems/__init__.py b/fishy/systems/__init__.py index 33dfae1..85c0a39 100644 --- a/fishy/systems/__init__.py +++ b/fishy/systems/__init__.py @@ -1,4 +1,3 @@ -from fishy.systems.controls import Control from fishy.systems.fishing_event import HookEvent, StickEvent, LookEvent, IdleEvent from fishy.systems.fishing_mode import FishingMode from fishy.systems.globals import G diff --git a/fishy/systems/config.json b/fishy/systems/config.json deleted file mode 100644 index 5e1b67f..0000000 --- a/fishy/systems/config.json +++ /dev/null @@ -1 +0,0 @@ -{"dark_mode": true} \ No newline at end of file diff --git a/fishy/systems/config.py b/fishy/systems/config.py index 8c07e70..025a044 100644 --- a/fishy/systems/config.py +++ b/fishy/systems/config.py @@ -1,5 +1,6 @@ import json import os +from threading import Thread filename = "config.json" @@ -14,10 +15,14 @@ class Config: return self.config_dict[key] return default - def set(self, key, value): + def set(self, key, value, save=True): self.config_dict[key] = value - self.save_config() + if save: + self.save_config() def save_config(self): - with open(filename, 'w') as f: - f.write(json.dumps(self.config_dict)) + def save(): + with open(filename, 'w') as f: + f.write(json.dumps(self.config_dict)) + + Thread(target=save).start() diff --git a/fishy/systems/controls.py b/fishy/systems/controls.py deleted file mode 100644 index f558f57..0000000 --- a/fishy/systems/controls.py +++ /dev/null @@ -1,89 +0,0 @@ -from enum import Enum - -from pynput.keyboard import Key - -class Control: - """ - Input system for the bot. - - variables - current: current mode of the input - controls: Maps different key to different keyword depending on the current mode - """ - current = 0 - - class Keywords(Enum): - """ - Enums which define different functionality to be called - used in `fishy.py` to run different code depending on which keyword is used - """ - SwitchMode = "switch mode" - StartPause = "start/pause" - Debug = "debug" - Stop = "stop" - ClearPrintOnce = "clear print once" - - controls = [ - { - "name": "SYSTEM", - "controls": [ - [Keywords.SwitchMode, Key.f8], - [Keywords.StartPause, Key.f9], - [Keywords.Stop, Key.f11] - ] - }, - { - "name": "DEBUG", - "controls": [ - [Keywords.SwitchMode, Key.f8], - [Keywords.ClearPrintOnce, Key.f9], - [Keywords.Debug, Key.f10], - ] - } - ] - - @staticmethod - def getControlHelp(): - """ - creates a control help string depending on the current mode - :return: string - """ - s = "\n\nCurrent Mode: " + Control.get()["name"]+"\n" - for c in Control.controls[Control.current]["controls"]: - try: - s += c[0].value + ": " + c[1].name + "\n" - except AttributeError: - s += c[0].value + ": " + c[1] + "\n" - - return s - - @staticmethod - def get(): - """ - returns the controls current mode control array - :return: control array - """ - return Control.controls[Control.current] - - @staticmethod - def find(key): - """ - converts key into the control keyword - :param key: key pressed - :return: corresponding keyword - """ - for c in Control.get()["controls"]: - if key == c[1]: - return c - - return None - - @staticmethod - def nextState(): - """ - Changes the current mode - """ - Control.current += 1 - - if Control.current >= len(Control.controls): - Control.current = 0 diff --git a/fishy/systems/fishing_event.py b/fishy/systems/fishing_event.py index 48cb812..16f765a 100644 --- a/fishy/systems/fishing_event.py +++ b/fishy/systems/fishing_event.py @@ -24,6 +24,9 @@ class FishEvent(ABC): class HookEvent(FishEvent): + def __init__(self, collect_r: bool): + self.collect_r = collect_r + def onEnterCallback(self, previousMode): """ called when the fish hook is detected @@ -39,7 +42,7 @@ class HookEvent(FishEvent): round_float(timeToHook)) + " secs. " + "Total: " + str(G.totalFishCaught)) pyautogui.press('e') - if G.arguments["--collect-r"]: + if self.collect_r: time.sleep(0.1) pyautogui.press('r') time.sleep(0.1) diff --git a/fishy/systems/fishing_mode.py b/fishy/systems/fishing_mode.py index f411acf..48d43e6 100644 --- a/fishy/systems/fishing_mode.py +++ b/fishy/systems/fishing_mode.py @@ -46,7 +46,7 @@ class FishingMode: return m @staticmethod - def Loop(hueValue, pause): + def Loop(hueValue): """ Executed in the start of the main loop in fishy.py Changes modes, calls mode events (callbacks) when mode is changed @@ -69,7 +69,7 @@ class FishingMode: if FishingMode.CurrentCount >= FishingMode.Threshold: FishingMode.CurrentMode = FishingMode.GetByLabel(current_label) - if not pause and FishingMode.CurrentMode != FishingMode.PrevMode and FishingMode.PrevMode is not None: + if FishingMode.CurrentMode != FishingMode.PrevMode and FishingMode.PrevMode is not None: if FishingMode.PrevMode.event is not None: FishingMode.PrevMode.event.onExitCallback(FishingMode.CurrentMode) diff --git a/fishy/systems/globals.py b/fishy/systems/globals.py index 91b32c4..d4b8c05 100644 --- a/fishy/systems/globals.py +++ b/fishy/systems/globals.py @@ -5,7 +5,5 @@ class G: fishCaught = 0 totalFishCaught = 0 stickInitTime = 0 - stop = False - pause = True debug = False arguments = {} diff --git a/fishy/systems/gui.py b/fishy/systems/gui.py index 798808e..739a056 100644 --- a/fishy/systems/gui.py +++ b/fishy/systems/gui.py @@ -1,15 +1,17 @@ import logging import time +import webbrowser from enum import Enum from logging import StreamHandler from tkinter import * from tkinter.ttk import * -from typing import Tuple, List, Callable +from typing import Tuple, List, Callable, Optional from ttkthemes import ThemedTk from waiting import wait import threading +from fishy.systems import helper from fishy.systems.config import Config @@ -24,8 +26,9 @@ class GUIStreamHandler(StreamHandler): class GUIEvent(Enum): - START_BUTTON = 0 # args: ip: str, action_key: str, fullscreen: bool + START_BUTTON = 0 # args: ip: str, action_key: str, fullscreen: bool, collect_r: bool CHECK_PIXELVAL = 1 + QUIT = 2 class GUIFunction(Enum): @@ -35,7 +38,7 @@ class GUIFunction(Enum): class GUI: - def __init__(self, config: Config, event_trigger: Callable[[GUIEvent, Tuple], None]): + def __init__(self, config: Config, event_trigger: Callable[[GUIEvent, Optional[Tuple]], None]): self.config = config self.start_restart = False self.destroyed = True @@ -49,6 +52,8 @@ class GUI: self.console = None self.start_button = None + self.thread = threading.Thread(target=self.create, args=()) + def create(self): self.root = ThemedTk(theme="equilux", background=True) self.root.title("Fiishybot for Elder Scrolls Online") @@ -65,9 +70,15 @@ class GUI: debug_menu = Menu(menubar, tearoff=0) debug_menu.add_command(label="Check PixelVal", - command=lambda: logging.error("Not Implemented")) + command=lambda: self._event_trigger(GUIEvent.CHECK_PIXELVAL)) debug_menu.add_command(label="Log Dump") - menubar.add_cascade(label="Debug", menu=debug_menu, command=lambda: logging.error("Not Implemented")) + menubar.add_cascade(label="Debug", menu=debug_menu) + + help_menu = Menu(menubar, tearoff=0) + help_menu.add_command(label="Need Help?", command=lambda: helper.open_web("http://discord.definex.in")) + help_menu.add_command(label="Donate Us", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar")) + menubar.add_cascade(label="Help", menu=help_menu) + self.root.config(menu=menubar) # endregion @@ -85,10 +96,11 @@ class GUI: Label(left_frame, text="IP").grid(row=0, column=0) ip = Entry(left_frame) + ip.insert(0, self.config.get("ip", "")) ip.grid(row=0, column=1) Label(left_frame, text="Fullscreen: ").grid(row=1, column=0, pady=(5, 5)) - borderless = Checkbutton(left_frame) + borderless = Checkbutton(left_frame, variable=IntVar(value=1 if self.config.get("borderless", False) else 0)) borderless.grid(row=1, column=1) left_frame.grid(row=0, column=0) @@ -98,19 +110,27 @@ class GUI: Label(right_frame, text="Action Key:").grid(row=0, column=0) action_key_entry = Entry(right_frame) action_key_entry.grid(row=0, column=1) - action_key_entry.insert(0, "e") + action_key_entry.insert(0, self.config.get("action_key", "e")) - Label(right_frame, text="Press start").grid(row=1, columnspan=2, pady=(5, 5)) + Label(right_frame, text="Collect R: ").grid(row=1, column=0, pady=(5, 5)) + collect_r = Checkbutton(right_frame, variable=IntVar(value=1 if self.config.get("collect_r", False) else 0)) + collect_r.grid(row=1, column=1) right_frame.grid(row=0, column=1, padx=(50, 0)) controls_frame.pack() - self.start_button = Button(self.root, text="START", width=25) - self.start_button["command"] = lambda: self._event_trigger(GUIEvent.START_BUTTON, (ip.get(), - action_key_entry.get(), - borderless.instate( - ['selected']))) + self.start_button = Button(self.root, text="STOP" if self._bot_running else "START", width=25) + + def start_button_callback(): + args = (ip.get(), + action_key_entry.get(), + borderless.instate(['selected']), + collect_r.instate(['selected'])) + self._event_trigger(GUIEvent.START_BUTTON, args) + self._save_config(*args) + + self.start_button["command"] = start_button_callback self.start_button.pack(pady=(15, 15)) # endregion @@ -129,6 +149,7 @@ class GUI: self.start_restart = False self.create() if self.destroyed: + self._event_trigger(GUIEvent.QUIT) break time.sleep(0.01) @@ -138,8 +159,9 @@ class GUI: if func[0] == GUIFunction.LOG: self._write_to_console(func[1][0]) - elif func[1] == GUIFunction.STARTED: - self.start_button["text"] = "STOP" if func[1][0] else "START" + elif func[0] == GUIFunction.STARTED: + self._bot_running = func[1][0] + self.start_button["text"] = "STOP" if self._bot_running else "START" def _apply_theme(self, dark): self.root["theme"] = "equilux" if dark else "breeze" @@ -164,25 +186,15 @@ class GUI: self.console.see("end") # scroll to bottom self.console['state'] = 'disabled' + def _save_config(self, ip, action_key, borderless, collect_r): + self.config.set("ip", ip, False) + self.config.set("action_key", action_key, False) + self.config.set("borderless", borderless, False) + self.config.set("collect_r", collect_r, False) + self.config.save_config() + def start(self): - threading.Thread(target=self.create, args=()).start() + self.thread.start() - def call(self, gui_func: GUIFunction, args): + def call(self, gui_func: GUIFunction, args: Tuple = None): self._function_queue.append((gui_func, args)) - -# def start(ip, actionkey, fullscreen): -# logging.info(f"{ip}, {actionkey}, {fullscreen}") -# -# -# def main(): -# config = Config() -# gui = GUI(config=config, gui_callback=GUICallback(start_callback=start)) -# gui.start() -# wait(lambda: not gui.destroyed) -# while not gui.destroyed: -# gui.writeToLog("yo") -# time.sleep(1) -# -# -# if __name__ == '__main__': -# main() diff --git a/fishy/systems/helper.py b/fishy/systems/helper.py index c30e1a4..dd6e7a3 100644 --- a/fishy/systems/helper.py +++ b/fishy/systems/helper.py @@ -1,5 +1,8 @@ +import logging import sys +import webbrowser from decimal import Decimal +from threading import Thread import cv2 import numpy as np @@ -38,3 +41,8 @@ def enable_full_array_printing(): (summarized arrays are printed by default) """ np.set_printoptions(threshold=sys.maxsize) + + +def open_web(website): + logging.debug("opening web, please wait...") + Thread(target=lambda: webbrowser.open(website, new=2)).start() diff --git a/fishy/systems/window.py b/fishy/systems/window.py index 6157d1d..862fe7d 100644 --- a/fishy/systems/window.py +++ b/fishy/systems/window.py @@ -35,23 +35,19 @@ class Window: self.scale = scale @staticmethod - def Init(): + def Init(borderless: bool): """ Executed once before the main loop, Finds the game window, and calculates the offset to remove the title bar """ - try: - Window.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") - rect = win32gui.GetWindowRect(Window.hwnd) - clientRect = win32gui.GetClientRect(Window.hwnd) - Window.windowOffset = math.floor(((rect[2] - rect[0]) - clientRect[2]) / 2) - Window.titleOffset = ((rect[3] - rect[1]) - clientRect[3]) - Window.windowOffset - if G.arguments["--borderless"]: - Window.titleOffset = 0 + Window.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") + rect = win32gui.GetWindowRect(Window.hwnd) + clientRect = win32gui.GetClientRect(Window.hwnd) + Window.windowOffset = math.floor(((rect[2] - rect[0]) - clientRect[2]) / 2) + Window.titleOffset = ((rect[3] - rect[1]) - clientRect[3]) - Window.windowOffset + if borderless: + Window.titleOffset = 0 - except pywintypes.error: - logging.info("Game window not found") - quit() @staticmethod def Loop():