re structured window into server client model so that multiple engine can use them simultaneously

This commit is contained in:
Adam Saudagar 2020-10-17 16:22:04 +05:30
parent 825ce11ced
commit 315adf9799
10 changed files with 230 additions and 175 deletions

View File

@ -8,7 +8,7 @@ import win32gui
import fishy
from fishy import web, helper, gui
from fishy.engine.event_handler import EngineEventHandler
from fishy.engine.common.event_handler import EngineEventHandler
from fishy.gui import GUI, splash
from fishy.helper import Config, hotkey

View File

View File

@ -0,0 +1,105 @@
import logging
from enum import Enum
from typing import List
import cv2
import imutils
from fishy.engine.common import window_server
from fishy.engine.common.window_server import WindowServer, Status
from fishy.helper import helper
class WindowClient:
clients: List['WindowClient'] = []
def __init__(self, crop=None, color=None, scale=None, show_name=None):
"""
create a window instance with these pre process
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
:param color: color to use example cv2.COLOR_RGB2HSV
:param scale: scaling the window
"""
self.color = color
self.crop = crop
self.scale = scale
self.show_name = show_name
self.showing = False
if len(WindowClient.clients) == 0:
window_server.start()
WindowClient.clients.append(self)
def __del__(self):
WindowClient.clients.remove(self)
if len(WindowClient.clients) == 0:
window_server.stop()
def get_capture(self):
"""
copies the recorded screen and then pre processes its
:return: game window image
"""
if WindowServer.status == Status.CRASHED:
return None
if not window_server.screen_ready():
logging.info("waiting fors screen...")
helper.wait_until(window_server.screen_ready)
logging.info("screen ready, continuing...")
temp_img = WindowServer.Screen
if temp_img is None:
return None
if self.color is not None:
temp_img = cv2.cvtColor(temp_img, self.color)
if self.crop is not None:
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
if self.scale is not None:
temp_img = cv2.resize(temp_img, (self.scale[0], self.scale[1]), interpolation=cv2.INTER_AREA)
return temp_img
def processed_image(self, func=None):
"""
processes the image using the function provided
:param func: function to process image
:return: processed image
"""
if WindowServer.status == Status.CRASHED:
return None
if func is None:
return self.get_capture()
else:
return func(self.get_capture())
def show(self, resize=None, func=None, ready_img=None):
"""
Displays the processed image for debugging purposes
:param ready_img: send ready image, just show the `ready_img` directly
:param name: unique name for the image, used to create a new window
:param resize: scale the image to make small images more visible
:param func: function to process the image
"""
if WindowServer.status == Status.CRASHED:
return
if not self.show_name:
logging.warning("You need to assign a name first")
return
if ready_img is None:
img = self.processed_image(func)
if resize is not None:
img = imutils.resize(img, width=resize)
else:
img = ready_img
cv2.imshow(self.show_name, img)
self.showing = True

View File

@ -0,0 +1,106 @@
import logging
from enum import Enum
from threading import Thread
import cv2
import math
import pywintypes
import win32gui
from win32api import GetSystemMetrics
import numpy as np
from PIL import ImageGrab
class Status(Enum):
CRASHED = -1,
STOPPED = 0,
RUNNING = 1
class WindowServer:
"""
Records the game window, and allows to create instance to process it
"""
Screen = None
windowOffset = None
titleOffset = None
hwnd = None
status = Status.STOPPED
def init(borderless: bool):
"""
Executed once before the main loop,
Finds the game window, and calculates the offset to remove the title bar
"""
try:
WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
rect = win32gui.GetWindowRect(WindowServer.hwnd)
client_rect = win32gui.GetClientRect(WindowServer.hwnd)
WindowServer.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2)
WindowServer.titleOffset = ((rect[3] - rect[1]) - client_rect[3]) - WindowServer.windowOffset
if borderless:
WindowServer.titleOffset = 0
WindowServer.status = Status.RUNNING
except pywintypes.error:
logging.error("Game window not found")
WindowServer.status = Status.CRASHED
def loop():
"""
Executed in the start of the main loop
finds the game window location and captures it
"""
bbox = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
temp_screen = np.array(ImageGrab.grab(bbox=bbox))
temp_screen = cv2.cvtColor(temp_screen, cv2.COLOR_BGR2RGB)
rect = win32gui.GetWindowRect(WindowServer.hwnd)
crop = (
rect[0] + WindowServer.windowOffset, rect[1] + WindowServer.titleOffset, rect[2] - WindowServer.windowOffset,
rect[3] - WindowServer.windowOffset)
WindowServer.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]]
if WindowServer.Screen.size == 0:
logging.error("Don't minimize or drag game window outside the screen")
WindowServer.status = Status.CRASHED
def loop_end():
cv2.waitKey(25)
from fishy.engine.common.window import WindowClient
for c in WindowClient.clients:
if not c.showing:
cv2.destroyWindow(c.show_name)
def run():
# todo use config
while WindowServer.status == Status.RUNNING:
loop()
loop_end()
def start():
if WindowServer.status == Status.RUNNING:
return
init(False)
if WindowServer.status == Status.RUNNING:
Thread(target=run).start()
def screen_ready():
return WindowServer.Screen is not None or WindowServer.status == Status.CRASHED
def stop():
WindowServer.status = Status.STOPPED

View File

@ -1,5 +1,4 @@
import math
from threading import Thread
import cv2
import logging
@ -10,31 +9,27 @@ import pywintypes
import pytesseract
from fishy.engine import SemiFisherEngine
from fishy.engine.common.window import WindowClient
from fishy.engine.semifisher import fishing_event
from fishy.engine.IEngine import IEngine
from fishy.engine.window import Window
from fishy.engine.common.IEngine import IEngine
from pynput import keyboard, mouse
from fishy.helper import Config, hotkey
from fishy.helper.helper import wait_until
from fishy.helper.hotkey import Key
mse = mouse.Controller()
kb = keyboard.Controller()
offset = 10
def sign(x):
return -1 if x < 0 else 1
offset = 10
def get_crop_coods(window):
Window.loop()
img = window.get_capture()
img = cv2.inRange(img, 0, 1)
Window.loop_end()
cnt, h = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
@ -99,14 +94,7 @@ class FullAuto(IEngine):
logging.info("Loading please wait...")
self.initalize_keys()
try:
Window.init(False)
except pywintypes.error:
logging.info("Game window not found")
self.toggle_start()
return
self.window = Window(color=cv2.COLOR_RGB2GRAY)
self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="Full auto debug")
self.window.crop = get_crop_coods(self.window)
self._tesseract_dir = self.config.get("tesseract_dir", None)
@ -119,12 +107,11 @@ class FullAuto(IEngine):
self.gui.bot_started(True)
while self.start:
Window.loop()
self.window.show("test", func=image_pre_process)
Window.loop_end()
self.window.show(func=image_pre_process)
cv2.waitKey(25)
self.gui.bot_started(False)
unassign_keys()
logging.info("Quit")
def get_coods(self):
return get_values_from_image(self.window.processed_image(func=image_pre_process), self._tesseract_dir)
@ -230,5 +217,5 @@ if __name__ == '__main__':
bot = FullAuto(c, None)
fisher = SemiFisherEngine(c, None)
hotkey.initalize()
# fisher.toggle_start()
fisher.toggle_start()
bot.toggle_start()

View File

@ -8,12 +8,12 @@ import logging
import pywintypes
from fishy.engine.IEngine import IEngine
from fishy.engine.common.IEngine import IEngine
from fishy.engine.semifisher import fishing_event
from .fishing_event import HookEvent, StickEvent, LookEvent, IdleEvent
from .fishing_mode import FishingMode
from .pixel_loc import PixelLoc
from fishy.engine.window import Window
from ..common.window import WindowClient
if typing.TYPE_CHECKING:
from fishy.gui import GUI
@ -31,16 +31,6 @@ class SemiFisherEngine(IEngine):
"""
action_key = self.config.get("action_key", "e")
borderless = self.config.get("borderless", False)
# initialize widow
# noinspection PyUnresolvedReferences
try:
Window.init(borderless)
except pywintypes.error:
logging.info("Game window not found")
self.start = False
return
# initializes fishing modes and their callbacks
FishingMode("hook", 0, HookEvent(action_key, False))
@ -48,27 +38,24 @@ class SemiFisherEngine(IEngine):
FishingMode("look", 2, LookEvent(action_key))
FishingMode("idle", 3, IdleEvent(self.config.get("uid"), self.config.get("sound_notification")))
self.fishPixWindow = Window(color=cv2.COLOR_RGB2HSV)
self.fishPixWindow = WindowClient(color=cv2.COLOR_RGB2HSV)
# check for game window and stuff
self.gui.bot_started(True)
logging.info("Starting the bot engine, look at the fishing hole to start fishing")
Thread(target=self._wait_and_check).start()
while self.start:
# Services to be ran in the start of the main loop
success = Window.loop()
capture = self.fishPixWindow.get_capture()
if not success:
if capture is None:
# if window server crashed
self.gui.bot_started(False)
self.toggle_start()
continue
# get the PixelLoc and find the color values, to give it to `FishingMode.Loop`
self.fishPixWindow.crop = PixelLoc.val
hue_value = self.fishPixWindow.get_capture()[0][0][0]
hue_value = capture[0][0][0]
FishingMode.loop(hue_value)
# Services to be ran in the end of the main loop
Window.loop_end()
logging.info("Fishing engine stopped")
self.gui.bot_started(False)

View File

@ -1,130 +0,0 @@
import logging
import cv2
import math
import win32gui
from win32api import GetSystemMetrics
import imutils
import numpy as np
from PIL import ImageGrab
class Window:
"""
Records the game window, and allows to create instance to process it
"""
Screen = None
windowOffset = None
titleOffset = None
hwnd = None
showing = False
def __init__(self, crop=None, color=None, scale=None):
"""
create a window instance with these pre process
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
:param color: color to use example cv2.COLOR_RGB2HSV
:param scale: scaling the window
"""
self.color = color
self.crop = crop
self.scale = scale
@staticmethod
def init(borderless: bool):
"""
Executed once before the main loop,
Finds the game window, and calculates the offset to remove the title bar
"""
Window.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
rect = win32gui.GetWindowRect(Window.hwnd)
client_rect = win32gui.GetClientRect(Window.hwnd)
Window.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2)
Window.titleOffset = ((rect[3] - rect[1]) - client_rect[3]) - Window.windowOffset
if borderless:
Window.titleOffset = 0
@staticmethod
def loop():
"""
Executed in the start of the main loop
finds the game window location and captures it
"""
Window.showing = False
bbox = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
temp_screen = np.array(ImageGrab.grab(bbox=bbox))
temp_screen = cv2.cvtColor(temp_screen, cv2.COLOR_BGR2RGB)
rect = win32gui.GetWindowRect(Window.hwnd)
crop = (rect[0] + Window.windowOffset, rect[1] + Window.titleOffset, rect[2] - Window.windowOffset,
rect[3] - Window.windowOffset)
Window.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]]
if Window.Screen.size == 0:
logging.info("Don't minimize or drag game window outside the screen")
return False
return True
@staticmethod
def loop_end():
"""
Executed in the end of the game loop
"""
cv2.waitKey(25)
if not Window.showing:
cv2.destroyAllWindows()
def get_capture(self):
"""
copies the recorded screen and then pre processes its
:return: game window image
"""
temp_img = Window.Screen
if self.color is not None:
temp_img = cv2.cvtColor(temp_img, self.color)
if self.crop is not None:
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
if self.scale is not None:
temp_img = cv2.resize(temp_img, (self.scale[0], self.scale[1]), interpolation=cv2.INTER_AREA)
return temp_img
def processed_image(self, func=None):
"""
processes the image using the function provided
:param func: function to process image
:return: processed image
"""
if func is None:
return self.get_capture()
else:
return func(self.get_capture())
def show(self, name, resize=None, func=None, ready_img=None):
"""
Displays the processed image for debugging purposes
:param ready_img: send ready image, just show the `ready_img` directly
:param name: unique name for the image, used to create a new window
:param resize: scale the image to make small images more visible
:param func: function to process the image
"""
if ready_img is None:
img = self.processed_image(func)
if resize is not None:
img = imutils.resize(img, width=resize)
else:
img = ready_img
cv2.imshow(name, img)
Window.showing = True

View File

@ -5,7 +5,7 @@ import threading
from ttkthemes import ThemedTk
from fishy.engine.event_handler import EngineEventHandler
from fishy.engine.common.event_handler import EngineEventHandler
from fishy.gui import config_top
from fishy.gui.funcs import GUIFuncs
from . import main_gui