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

This commit is contained in:
DESKTOP-JVKHS7I\Adam 2020-04-18 17:02:14 +05:30
parent 9a6f46f02d
commit faf2c62e9c
12 changed files with 155 additions and 220 deletions

3
.gitignore vendored
View File

@ -7,4 +7,5 @@ template_folder/
checkpoint
venv/
*.pickle
fishy.egg-info/
fishy.egg-info/
config.json

View File

@ -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__":

View File

@ -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

View File

@ -1 +0,0 @@
{"dark_mode": true}

View File

@ -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()

View File

@ -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

View File

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

View File

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

View File

@ -5,7 +5,5 @@ class G:
fishCaught = 0
totalFishCaught = 0
stickInitTime = 0
stop = False
pause = True
debug = False
arguments = {}

View File

@ -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()

View File

@ -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()

View File

@ -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():