full auto bug fixing

- changed engine.toggle_start to abstract method
- check for state before turning off bot engine
- added and corrected logging messages for contorls and calibration
- callibrate._get_factor returning None fixed
- optimized controls
- removed config for show crop
- recorder now runs asksaveasfile in gui thread and blocks itself till resoponse
- corrrected logic for saving failed files
- semifisher dont log started bot engine if not ran from gui
- added file name label in full auto config top
- call in thread now can return values
- gui now saves location when closed
- dynamic config location, now uses correct document folder if config file is not found in incorrect document folder
This commit is contained in:
Adam Saudagar 2020-12-01 02:34:33 +05:30
parent 367e2bea55
commit a964c65776
14 changed files with 133 additions and 63 deletions

View File

@ -24,11 +24,9 @@ class IEngine(ABC):
return self.get_gui().funcs return self.get_gui().funcs
@abstractmethod
def toggle_start(self): def toggle_start(self):
self.start = not self.start ...
if self.start:
self.thread = Thread(target=self.run)
self.thread.start()
@abstractmethod @abstractmethod
def run(self): def run(self):

View File

@ -44,7 +44,7 @@ def _update_factor(key, value):
def _get_factor(key): def _get_factor(key):
config.get("full_auto_factors", {}).get(key) return config.get("full_auto_factors", {}).get(key)
class Calibrate: class Calibrate:
@ -72,7 +72,7 @@ class Calibrate:
# endregion # endregion
def all_callibrated(self): def all_callibrated(self):
return self.crop and self.move_factor and self.rot_factor return self.crop is not None and self.move_factor is not None and self.rot_factor is not None
def toggle_show(self): def toggle_show(self):
self.engine.show_crop = not self.engine.show_crop self.engine.show_crop = not self.engine.show_crop
@ -81,8 +81,8 @@ class Calibrate:
if enable_crop: if enable_crop:
self.engine.show_crop = True self.engine.show_crop = True
crop = get_crop_coods(self.engine.window) crop = get_crop_coods(self.engine.window)
self.engine.window.crop = self.engine.crop
_update_factor("crop", crop) _update_factor("crop", crop)
self.engine.window.crop = crop
def walk_calibrate(self): def walk_calibrate(self):
walking_time = 3 walking_time = 3
@ -105,6 +105,7 @@ class Calibrate:
move_factor = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / walking_time move_factor = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / walking_time
_update_factor("move_factor", move_factor) _update_factor("move_factor", move_factor)
logging.info("done")
def rotate_calibrate(self): def rotate_calibrate(self):
rotate_times = 50 rotate_times = 50
@ -128,6 +129,7 @@ class Calibrate:
rot_factor = (rot3 - rot2) / rotate_times rot_factor = (rot3 - rot2) / rotate_times
_update_factor("rot_factor", rot_factor) _update_factor("rot_factor", rot_factor)
logging.info("done")
def time_to_reach_bottom_callibrate(self): def time_to_reach_bottom_callibrate(self):
self._callibrate_state = 0 self._callibrate_state = 0
@ -135,12 +137,12 @@ class Calibrate:
def _f8_pressed(): def _f8_pressed():
self._callibrate_state += 1 self._callibrate_state += 1
logging.info("Now loop up and press f8") logging.info("look straight up and press f8")
hotkey.set_hotkey(Key.F8, _f8_pressed) hotkey.set_hotkey(Key.F8, _f8_pressed)
wait_until(lambda: self._callibrate_state == 1) wait_until(lambda: self._callibrate_state == 1)
logging.info("looking down now, as soon as you look on the floor, press f8 again") logging.info("as soon as you look on the floor, press f8 again")
y_cal_start_time = time.time() y_cal_start_time = time.time()
while self._callibrate_state == 1: while self._callibrate_state == 1:
@ -150,3 +152,4 @@ class Calibrate:
time_to_reach_bottom = time.time() - y_cal_start_time time_to_reach_bottom = time.time() - y_cal_start_time
_update_factor("time_to_reach_bottom", time_to_reach_bottom) _update_factor("time_to_reach_bottom", time_to_reach_bottom)
logging.info("done")

View File

@ -2,20 +2,19 @@ import logging
from fishy.helper import hotkey, helper from fishy.helper import hotkey, helper
from fishy.engine.fullautofisher.engine import FullAuto from fishy.engine.fullautofisher.engine import FullAuto, State
from fishy.helper.hotkey import Key from fishy.helper.hotkey import Key
def get_controls(engine: FullAuto): def get_controls(engine: FullAuto):
from fishy.engine.fullautofisher.recorder import Recorder from fishy.engine.fullautofisher.recorder import Recorder
from fishy.engine.fullautofisher.player import Player from fishy.engine.fullautofisher.player import Player
controls = [ controls = [
("MODE_SELECT", { ("MODE_SELECT", {
Key.RIGHT: (lambda: engine.controls.select_mode("CALIBRATE"), "calibrate mode"), Key.RIGHT: (lambda: engine.controls.select_mode("CALIBRATE"), "calibrate mode"),
Key.UP: (lambda: engine.controls.select_mode("PLAY"), "play mode"), Key.UP: (lambda: engine.controls.select_mode("TEST1"), "test mode"),
Key.LEFT: (lambda: engine.controls.select_mode("RECORD"), "record mode"), Key.LEFT: (Player(engine).toggle_move, "start/stop play"),
Key.DOWN: (lambda: engine.controls.select_mode("TEST"), "test mode") Key.DOWN: (Recorder(engine).toggle_recording, "start/stop record"),
}), }),
("CALIBRATE", { ("CALIBRATE", {
Key.RIGHT: (engine.calibrate.update_crop, "cropping"), Key.RIGHT: (engine.calibrate.update_crop, "cropping"),
@ -23,16 +22,10 @@ def get_controls(engine: FullAuto):
Key.LEFT: (engine.calibrate.rotate_calibrate, "rotation"), Key.LEFT: (engine.calibrate.rotate_calibrate, "rotation"),
Key.DOWN: (engine.calibrate.time_to_reach_bottom_callibrate, "look up down") Key.DOWN: (engine.calibrate.time_to_reach_bottom_callibrate, "look up down")
}), }),
("PLAY/RECORD", {
Key.RIGHT: (Player(engine).toggle_move, "start/stop play"),
Key.UP: (Recorder(engine).toggle_recording, "start/stop record"),
Key.LEFT: (None, "not implemented"),
Key.DOWN: (None, "not implemented")
}),
("TEST1", { ("TEST1", {
Key.RIGHT: (engine.test.print_coods, "print coordinates"), Key.RIGHT: (engine.test.print_coods, "print coordinates"),
Key.UP: (engine.test.look_for_hole, "look for hole up down"), Key.UP: (engine.test.look_for_hole, "look for hole up down"),
Key.LEFT: (None, "not implemented"), Key.LEFT: (None, ""),
Key.DOWN: (lambda: engine.controls.select_mode("TEST2"), "show next") Key.DOWN: (lambda: engine.controls.select_mode("TEST2"), "show next")
}), }),
("TEST2", { ("TEST2", {
@ -52,20 +45,27 @@ class Controls:
self.controls = controls self.controls = controls
def initialize(self): def initialize(self):
self.select_mode("MODE_SELECT") self.select_mode(self.controls[0][0])
def log_help(self):
help_str = f"\nCONTROLS: {self.controls[self.current_menu][0]}"
for key, meta in self.controls[self.current_menu][1].items():
func, name = meta
if func:
hotkey.set_hotkey(key, func)
help_str += f"\n{key.value}: {name}"
logging.info(help_str)
def select_mode(self, mode): def select_mode(self, mode):
if FullAuto.state != State.NONE:
self.log_help()
return
self.current_menu = 0 self.current_menu = 0
for i, control in enumerate(self.controls): for i, control in enumerate(self.controls):
if mode == control[0]: if mode == control[0]:
self.current_menu = i self.current_menu = i
self.log_help()
help_str = F"CONTROLS: {self.controls[self.current_menu][0]}"
for key, meta in self.controls[self.current_menu][1].items():
func, name = meta
hotkey.set_hotkey(key, func)
help_str += f"\n{key.value}: {name}"
logging.info(help_str)
def unassign_keys(self): def unassign_keys(self):
keys = [] keys = []

View File

@ -4,6 +4,7 @@ import tempfile
import traceback import traceback
import uuid import uuid
from enum import Enum from enum import Enum
from threading import Thread
from zipfile import ZipFile from zipfile import ZipFile
import cv2 import cv2
@ -69,17 +70,12 @@ class FullAuto(IEngine):
self.calibrate = Calibrate(self) self.calibrate = Calibrate(self)
self.test = Test(self) self.test = Test(self)
self.controls = Controls(controls.get_controls(self)) self.controls = Controls(controls.get_controls(self))
self.show_crop = False
@property
def show_crop(self):
return config.get("show_window_full_auto", False)
@show_crop.setter
def show_crop(self, x):
config.set("show_window_full_auto", x)
def run(self): def run(self):
logging.info("Loading please wait...") self.show_crop = False
FullAuto.state = State.NONE
self.gui.bot_started(True) self.gui.bot_started(True)
fishing_event.unsubscribe() fishing_event.unsubscribe()
self.fisher.toggle_start() self.fisher.toggle_start()
@ -95,6 +91,9 @@ class FullAuto(IEngine):
logging.info("tesseract not found") logging.info("tesseract not found")
downlaoad_and_extract_tesseract() downlaoad_and_extract_tesseract()
if not self.calibrate.all_callibrated():
logging.error("you need to callibrate first")
self.controls.initialize() self.controls.initialize()
while self.start and WindowClient.running(): while self.start and WindowClient.running():
self.window.show(self.show_crop, func=image_pre_process) self.window.show(self.show_crop, func=image_pre_process)
@ -103,7 +102,7 @@ class FullAuto(IEngine):
except: except:
traceback.print_exc() traceback.print_exc()
if not self.window.get_capture(): if self.window.get_capture() is None:
logging.error("Game window not found") logging.error("Game window not found")
self.gui.bot_started(False) self.gui.bot_started(False)
@ -203,6 +202,16 @@ class FullAuto(IEngine):
time.sleep(0.05) time.sleep(0.05)
self._curr_rotate_y -= 0.05 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)
self.thread.start()
if __name__ == '__main__': if __name__ == '__main__':
logging.getLogger("").setLevel(logging.DEBUG) logging.getLogger("").setLevel(logging.DEBUG)

View File

@ -41,6 +41,8 @@ class Player:
self.start_moving_flag = not self.start_moving_flag self.start_moving_flag = not self.start_moving_flag
if self.start_moving_flag: if self.start_moving_flag:
self._start_route() self._start_route()
else:
logging.info("Waiting for the last action to finish...")
def _hole_complete_callback(self, e): def _hole_complete_callback(self, e):
if e == fishing_event.State.IDLE: if e == fishing_event.State.IDLE:
@ -52,7 +54,6 @@ class Player:
if not timeline: if not timeline:
return return
logging.info("starting, press f8 to stop")
forward = True forward = True
i = 0 i = 0
while self.start_moving_flag: while self.start_moving_flag:

View File

@ -34,7 +34,7 @@ class Recorder:
def _start_recording(self): def _start_recording(self):
FullAuto.state = State.RECORDING FullAuto.state = State.RECORDING
logging.info("f8 to stop recording") logging.info("starting, press f8 to mark hole")
hotkey.set_hotkey(Recorder.mark_hole_key, self._mark_hole) hotkey.set_hotkey(Recorder.mark_hole_key, self._mark_hole)
self.timeline = [] self.timeline = []
@ -54,10 +54,15 @@ class Recorder:
hotkey.free_key(Recorder.mark_hole_key) hotkey.free_key(Recorder.mark_hole_key)
files = [('Fishy File', '*.fishy')] def func():
file = None _file = None
while not file: files = [('Fishy File', '*.fishy')]
file = asksaveasfile(mode='wb', filetypes=files, defaultextension=files) 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} data = {"full_auto_path": self.timeline}
pprint(data) pprint(data)
pickle.dump(data, file) pickle.dump(data, file)

View File

@ -2,6 +2,7 @@ import logging
import os import os
import tempfile import tempfile
import uuid import uuid
from datetime import datetime
from zipfile import ZipFile from zipfile import ZipFile
import cv2 import cv2
@ -9,6 +10,7 @@ import cv2
import pytesseract import pytesseract
from fishy.helper.downloader import download_file_from_google_drive from fishy.helper.downloader import download_file_from_google_drive
from fishy.helper.helper import get_documents
directory = os.path.join(os.environ["APPDATA"], "Tesseract-OCR") directory = os.path.join(os.environ["APPDATA"], "Tesseract-OCR")
@ -44,5 +46,5 @@ def get_values_from_image(img):
return float(vals[0]), float(vals[1]), float(vals[2]) return float(vals[0]), float(vals[1]), float(vals[2])
except Exception: except Exception:
logging.error("Couldn't read coods, make sure 'crop' calibration is correct") logging.error("Couldn't read coods, make sure 'crop' calibration is correct")
cv2.imwrite(f"fail_{str(uuid.uuid4())[:8]}", img) cv2.imwrite(os.path.join(get_documents(), "fishy_failed_reads", f"{datetime.now()}.jpg"), img)
return None return None

View File

@ -35,7 +35,9 @@ class SemiFisherEngine(IEngine):
# check for game window and stuff # check for game window and stuff
self.gui.bot_started(True) self.gui.bot_started(True)
logging.info("Starting the bot engine, look at the fishing hole to start fishing")
if self.get_gui:
logging.info("Starting the bot engine, look at the fishing hole to start fishing")
Thread(target=self._wait_and_check).start() Thread(target=self._wait_and_check).start()
while self.start and WindowClient.running(): while self.start and WindowClient.running():
capture = self.fishPixWindow.get_capture() capture = self.fishPixWindow.get_capture()
@ -74,6 +76,12 @@ class SemiFisherEngine(IEngine):
time.sleep(5) time.sleep(5)
Thread(target=show, args=()).start() Thread(target=show, args=()).start()
def toggle_start(self):
self.start = not self.start
if self.start:
self.thread = Thread(target=self.run)
self.thread.start()
if __name__ == '__main__': if __name__ == '__main__':
logging.getLogger("").setLevel(logging.DEBUG) logging.getLogger("").setLevel(logging.DEBUG)

View File

@ -1,4 +1,5 @@
import logging import logging
import os
import typing import typing
from tkinter.filedialog import askopenfilename from tkinter.filedialog import askopenfilename
@ -21,6 +22,12 @@ def start_fullfisher_config(gui: 'GUI'):
controls_frame = Frame(top) controls_frame = Frame(top)
top.title("Config") top.title("Config")
def file_name():
file = config.get("full_auto_rec_file", None)
if file is None:
return "Not Selected"
return os.path.basename(file)
def select_file(): def select_file():
file = askopenfilename(filetypes=[('Python Files', '*.fishy')]) file = askopenfilename(filetypes=[('Python Files', '*.fishy')])
if not file: if not file:
@ -29,8 +36,12 @@ def start_fullfisher_config(gui: 'GUI'):
config.set("full_auto_rec_file", file) config.set("full_auto_rec_file", file)
logging.info(f"loaded {file}") logging.info(f"loaded {file}")
Button(controls_frame, text="Select fishy file", command=select_file).grid(row=0, column=0) file_name_label.set(file_name())
Label(controls_frame, text="Use semi-fisher config for rest").grid(row=2, column=0)
file_name_label = StringVar(value=file_name())
Label(controls_frame, textvariable=file_name_label).grid(row=0, column=0)
Button(controls_frame, text="Select fishy file", command=select_file).grid(row=0, column=1)
Label(controls_frame, text="Use semi-fisher config for rest").grid(row=2, column=0, columnspan=2)
controls_frame.pack(padx=(5, 5), pady=(5, 5)) controls_frame.pack(padx=(5, 5), pady=(5, 5))
top.start() top.start()

View File

@ -5,6 +5,8 @@ from tkinter.ttk import *
import typing import typing
from fishy.helper import helper
from fishy.web import web from fishy.web import web
from fishy.libs.tkhtmlview import HTMLLabel from fishy.libs.tkhtmlview import HTMLLabel

View File

@ -1,6 +1,7 @@
import logging import logging
import uuid
from tkinter import OptionMenu, Button, IntVar from tkinter import OptionMenu, Button, IntVar
from typing import List, Callable, Optional from typing import List, Callable, Optional, Dict, Any
import threading import threading
from fishy.web import web from fishy.web import web
@ -12,6 +13,7 @@ from fishy.gui.funcs import GUIFuncs
from . import main_gui from . import main_gui
from .log_config import GUIStreamHandler from .log_config import GUIStreamHandler
from ..helper.config import config from ..helper.config import config
from ..helper.helper import wait_until
class GUI: class GUI:
@ -23,7 +25,8 @@ class GUI:
self._start_restart = False self._start_restart = False
self._destroyed = True self._destroyed = True
self._log_strings = [] self._log_strings = []
self._function_queue: List[Callable] = [] self._function_queue: Dict[str, Callable] = {}
self._result_queue: Dict[str, Any] = {}
self._bot_running = False self._bot_running = False
# UI items # UI items
@ -70,11 +73,20 @@ class GUI:
def _clear_function_queue(self): def _clear_function_queue(self):
while len(self._function_queue) > 0: while len(self._function_queue) > 0:
func = self._function_queue.pop(0) _id, func = self._function_queue.popitem()
func() result = func()
self._result_queue[_id] = result
def call_in_thread(self, func: Callable): def call_in_thread(self, func: Callable, block=False):
self._function_queue.append(func) _id = str(uuid.uuid4())
self._function_queue[_id] = func
if not block:
return None
wait_until(lambda: _id in self._result_queue)
return self._result_queue.pop(_id)
def _get_start_stop_text(self): def _get_start_stop_text(self):
return "STOP (F9)" if self._bot_running else "START (F9)" return "STOP (F9)" if self._bot_running else "START (F9)"

View File

@ -32,7 +32,6 @@ def _create(gui: 'GUI'):
gui._root = ThemedTk(theme="equilux", background=True) gui._root = ThemedTk(theme="equilux", background=True)
gui._root.title("Fishybot for Elder Scrolls Online") gui._root.title("Fishybot for Elder Scrolls Online")
gui._root.iconbitmap(helper.manifest_file('icon.ico')) gui._root.iconbitmap(helper.manifest_file('icon.ico'))
# region menu # region menu
@ -111,6 +110,8 @@ def _create(gui: 'GUI'):
_apply_theme(gui) _apply_theme(gui)
gui._root.update() gui._root.update()
gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10) gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10)
if config.get("win_loc") is not None:
gui._root.geometry(config.get("win_loc"))
hotkey.set_hotkey(Key.F9, gui.funcs.start_engine) hotkey.set_hotkey(Key.F9, gui.funcs.start_engine)
@ -118,8 +119,10 @@ def _create(gui: 'GUI'):
def set_destroy(): def set_destroy():
if gui._bot_running: if gui._bot_running:
logging.info("Turn off the bot engine first") logging.info("Turn off the bot engine first")
else: return
gui._destroyed = True
config.set("win_loc", gui._root.geometry())
gui._destroyed = True
gui._root.protocol("WM_DELETE_WINDOW", set_destroy) gui._root.protocol("WM_DELETE_WINDOW", set_destroy)
gui._destroyed = False gui._destroyed = False

View File

@ -6,7 +6,16 @@ import json
import os import os
# path to save the configuration file # path to save the configuration file
filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Documents", "fishy_config.json")
def filename():
from fishy.helper.helper import get_documents
name = "fishy_config.json"
_filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Documents", name)
if os.path.exists(_filename):
return _filename
return os.path.join(get_documents(), name)
class Config: class Config:
@ -16,7 +25,7 @@ class Config:
cache the configuration in a dict for faster access, cache the configuration in a dict for faster access,
if file is not found initialize the dict if file is not found initialize the dict
""" """
self.config_dict = json.loads(open(filename).read()) if os.path.exists(filename) else dict() self.config_dict = json.loads(open(filename()).read()) if os.path.exists(filename()) else dict()
def get(self, key, default=None): def get(self, key, default=None):
""" """
@ -51,7 +60,7 @@ class Config:
""" """
save the cache to the file save the cache to the file
""" """
with open(filename, 'w') as f: with open(filename(), 'w') as f:
f.write(json.dumps(self.config_dict)) f.write(json.dumps(self.config_dict))

View File

@ -12,13 +12,12 @@ from uuid import uuid1
from hashlib import md5 from hashlib import md5
from win32com.client import Dispatch from win32com.client import Dispatch
from win32comext.shell import shell, shellcon
import fishy import fishy
import winshell import winshell
from fishy import web from fishy import web
from . import Config
from .config import config
def not_implemented(): def not_implemented():
@ -49,6 +48,8 @@ def open_web(website):
def initialize_uid(): def initialize_uid():
from .config import config
if config.get("uid") is not None: if config.get("uid") is not None:
return return
@ -106,6 +107,8 @@ def manifest_file(rel_path):
def create_shortcut_first(): def create_shortcut_first():
from .config import config
if not config.get("shortcut_created", False): if not config.get("shortcut_created", False):
create_shortcut(False) create_shortcut(False)
config.set("shortcut_created", True) config.set("shortcut_created", True)
@ -159,5 +162,9 @@ def check_addon(name):
logging.error("couldn't install addon, try doing it manually") logging.error("couldn't install addon, try doing it manually")
def get_documents():
return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
def restart(): def restart():
os.execl(sys.executable, *([sys.executable] + sys.argv)) os.execl(sys.executable, *([sys.executable] + sys.argv))