mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
Merge pull request #76 from fishyboteso/feature/fullauto
Full auto improvements
This commit is contained in:
commit
381f573109
@ -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 = []
|
||||
|
||||
|
@ -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]:
|
||||
|
@ -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)
|
||||
|
0
fishy/engine/fullautofisher/mode/__init__.py
Normal file
0
fishy/engine/fullautofisher/mode/__init__.py
Normal file
@ -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")
|
14
fishy/engine/fullautofisher/mode/imode.py
Normal file
14
fishy/engine/fullautofisher/mode/imode.py
Normal file
@ -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):
|
||||
...
|
119
fishy/engine/fullautofisher/mode/player.py
Normal file
119
fishy/engine/fullautofisher/mode/player.py
Normal file
@ -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
|
149
fishy/engine/fullautofisher/mode/recorder.py
Normal file
149
fishy/engine/fullautofisher/mode/recorder.py
Normal file
@ -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()
|
@ -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
|
@ -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")
|
||||
|
@ -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
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user