mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
code documented and version incremented for the total fish caught feature
This commit is contained in:
parent
6a2326b818
commit
7d78347a4a
27
controls.py
27
controls.py
@ -2,9 +2,20 @@ from init import *
|
||||
|
||||
|
||||
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 = 1 if arguments["--debug"] else 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"
|
||||
@ -33,6 +44,10 @@ class Control:
|
||||
|
||||
@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"]:
|
||||
s += c[0].value + ": " + c[1].name + "\n"
|
||||
@ -41,10 +56,19 @@ class Control:
|
||||
|
||||
@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
|
||||
@ -53,6 +77,9 @@ class Control:
|
||||
|
||||
@staticmethod
|
||||
def nextState():
|
||||
"""
|
||||
Changes the current mode
|
||||
"""
|
||||
Control.current += 1
|
||||
|
||||
if Control.current >= len(Control.controls):
|
||||
|
@ -1,5 +1,9 @@
|
||||
from fishing_mode import *
|
||||
|
||||
"""
|
||||
Defines different fishing modes (states) which acts as state for state machine
|
||||
also implements callbacks which is called when states are changed
|
||||
"""
|
||||
|
||||
class FishEvent(ABC):
|
||||
@abstractmethod
|
||||
@ -11,8 +15,14 @@ class FishEvent(ABC):
|
||||
pass
|
||||
|
||||
class HookEvent(FishEvent):
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
"""
|
||||
called when the fish hook is detected
|
||||
increases the `fishCaught` and `totalFishCaught`, calculates the time it took to catch
|
||||
presses e to catch the fish
|
||||
|
||||
:param previousMode: previous mode in the state machine
|
||||
"""
|
||||
G.fishCaught += 1
|
||||
G.totalFishCaught += 1
|
||||
timeToHook = time.time() - G.stickInitTime
|
||||
@ -30,8 +40,14 @@ class HookEvent(FishEvent):
|
||||
|
||||
|
||||
class LookEvent(FishEvent):
|
||||
|
||||
"""
|
||||
state when looking on a fishing hole
|
||||
"""
|
||||
def onEnterCallback(self, previousMode):
|
||||
"""
|
||||
presses e to throw the fishing rod
|
||||
:param previousMode: previous mode in the state machine
|
||||
"""
|
||||
pyautogui.press('e')
|
||||
|
||||
def onExitCallback(self, currentMode):
|
||||
@ -39,11 +55,23 @@ class LookEvent(FishEvent):
|
||||
|
||||
|
||||
class IdleEvent(FishEvent):
|
||||
"""
|
||||
State when the fishing hole is depleted or the bot is doing nothing
|
||||
"""
|
||||
|
||||
def __init__(self, use_net):
|
||||
"""
|
||||
sets the flag to send notification on phone
|
||||
:param use_net: true if user wants to send notification on phone
|
||||
"""
|
||||
self.use_net = use_net
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
"""
|
||||
Resets the fishCaught counter and logs a message depending on the previous state
|
||||
:param previousMode: previous mode in the state machine
|
||||
"""
|
||||
|
||||
G.fishCaught = 0
|
||||
if self.use_net:
|
||||
net.sendHoleDeplete(G.fishCaught)
|
||||
@ -58,8 +86,15 @@ class IdleEvent(FishEvent):
|
||||
|
||||
|
||||
class StickEvent(FishEvent):
|
||||
"""
|
||||
State when fishing is going on
|
||||
"""
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
"""
|
||||
resets the fishing timer
|
||||
:param previousMode: previous mode in the state machine
|
||||
"""
|
||||
G.stickInitTime = time.time()
|
||||
|
||||
def onExitCallback(self, currentMode):
|
||||
|
@ -2,6 +2,16 @@ from window import *
|
||||
|
||||
|
||||
class FishingMode:
|
||||
"""
|
||||
State machine for fishing modes
|
||||
|
||||
HValues hue values for each fishing mode
|
||||
CuurentCount number of times same hue color is read before it changes state
|
||||
CurrentMode current mode of the state machine
|
||||
PrevMode previous mode of the state machine
|
||||
FishingStarted probably does nothing (not sure though)
|
||||
Modes list of states
|
||||
"""
|
||||
HValues = [60, 18, 100]
|
||||
Threshold = int(arguments["--hook-threshold"])
|
||||
|
||||
@ -14,6 +24,13 @@ class FishingMode:
|
||||
Modes = []
|
||||
|
||||
def __init__(self, name, label, event):
|
||||
"""
|
||||
create a new state
|
||||
:param name: name of the state
|
||||
:param label: integer, label of the state (int)
|
||||
:param event: object of class containing onEnterCallback & onExitCallback functions
|
||||
which are called when state is changed
|
||||
"""
|
||||
self.name = name
|
||||
self.label = label
|
||||
self.event = event
|
||||
@ -22,13 +39,24 @@ class FishingMode:
|
||||
|
||||
@staticmethod
|
||||
def GetByLabel(label):
|
||||
"""
|
||||
find a state using label
|
||||
:param label: label integer
|
||||
:return: state
|
||||
"""
|
||||
for m in FishingMode.Modes:
|
||||
if m.label == label:
|
||||
return m
|
||||
|
||||
|
||||
@staticmethod
|
||||
def Loop(hueValue, pause):
|
||||
"""
|
||||
Executed in the start of the main loop in fishy.py
|
||||
Changes modes, calls mode events (callbacks) when mode is changed
|
||||
|
||||
:param hueValue: huevValue read by the bot
|
||||
:param pause: true if bot is paused or not started
|
||||
"""
|
||||
current_label = 3
|
||||
for i, val in enumerate(FishingMode.HValues):
|
||||
if hueValue == val:
|
||||
@ -53,4 +81,3 @@ class FishingMode:
|
||||
FishingMode.CurrentMode.event.onEnterCallback(FishingMode.PrevMode)
|
||||
|
||||
FishingMode.PrevMode = FishingMode.CurrentMode
|
||||
|
||||
|
23
fishy.py
23
fishy.py
@ -1,11 +1,13 @@
|
||||
from pixel_loc import *
|
||||
|
||||
"""
|
||||
Start reading from `init.py`
|
||||
"""
|
||||
|
||||
def on_release(key):
|
||||
"""
|
||||
Read input
|
||||
:param key: key released
|
||||
:return: void
|
||||
Reads input, finds out the resultant action and performs it
|
||||
|
||||
:param key: key pressed
|
||||
"""
|
||||
|
||||
c = Control.find(key)
|
||||
@ -45,8 +47,8 @@ def hsv2rgb(img):
|
||||
|
||||
def startFishing():
|
||||
"""
|
||||
Starts the fishing codde
|
||||
:return: void
|
||||
Starts the fishing
|
||||
code explained in comments in detail
|
||||
"""
|
||||
|
||||
use_net = arguments["--ip"] is not None
|
||||
@ -55,6 +57,7 @@ def startFishing():
|
||||
|
||||
sleepFor = (1 / float(arguments["--check-frequency"]))
|
||||
|
||||
# initializes fishing modes and their callbacks
|
||||
FishingMode("hook", 0, HookEvent())
|
||||
FishingMode("stick", 1, StickEvent())
|
||||
FishingMode("look", 2, LookEvent())
|
||||
@ -64,24 +67,32 @@ def startFishing():
|
||||
|
||||
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
|
||||
current_time = time.time()
|
||||
|
||||
# Services to be ran in the start of the main loop
|
||||
Window.Loop()
|
||||
Log.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)
|
||||
Log.ou(str(FishingMode.CurrentMode.label) + ":" + str(fishPixWindow.getCapture()[0][0]))
|
||||
|
||||
# Services to be ran in the end of the main loop
|
||||
Log.LoopEnd()
|
||||
Window.LoopEnd()
|
||||
|
||||
# calculate the time it took to execute one loop of code, if it is more than the expected time warn user
|
||||
frameTime = time.time() - current_time
|
||||
if frameTime < sleepFor:
|
||||
time.sleep(sleepFor - frameTime)
|
||||
|
47
init.py
47
init.py
@ -14,12 +14,13 @@ Options:
|
||||
--debug Start program in debug controls.
|
||||
"""
|
||||
|
||||
VERSION = "0.1.1"
|
||||
VERSION = "0.1.2"
|
||||
print("Fishy " + VERSION + " for Elder Scrolls Online")
|
||||
|
||||
try:
|
||||
from docopt import docopt
|
||||
|
||||
# docopt checks if cli args are correct, if its not, it shows the correct syntax and quits
|
||||
arguments = docopt(__doc__)
|
||||
if arguments["--version"]:
|
||||
quit()
|
||||
@ -48,21 +49,27 @@ except Exception:
|
||||
|
||||
'''
|
||||
import stack
|
||||
shows the sequence of execution of each python scripts (bottom to up)
|
||||
scripts which are higher on the stack depends on the scripts below them
|
||||
script which is on top is called which then imports ones below it
|
||||
each scrip creates different services which is then used inside `fishy.py script
|
||||
|
||||
fishy
|
||||
pixel_loc
|
||||
fishing_event
|
||||
fishing_mode
|
||||
window
|
||||
log
|
||||
controls
|
||||
init
|
||||
fishy Reads input and uses `controls.py` to execute different commands
|
||||
Contains the main loop of the bot,
|
||||
calls different services and helps them to communicate to each other
|
||||
pixel_loc finds the location of the pixel which is used to detect different states (called PixelLoc)
|
||||
fishing_event implements different states along with their callbacks (idle, stick, hooked, look)
|
||||
fishing_mode state machine for different fishing mode
|
||||
window recodes the game window, and gives method to process them
|
||||
log very simple logging functions
|
||||
controls creates an input system for the bot
|
||||
init initializes global variables and imports all the libraries
|
||||
'''
|
||||
|
||||
|
||||
class G:
|
||||
"""
|
||||
Initialize global variables
|
||||
Initialize global variables used by different services
|
||||
"""
|
||||
fishCaught = 0
|
||||
totalFishCaught = 0
|
||||
@ -76,6 +83,13 @@ class G:
|
||||
|
||||
|
||||
def round_float(v, ndigits=2, rt_str=False):
|
||||
"""
|
||||
Rounds float
|
||||
:param v: float ot round off
|
||||
:param ndigits: round off to ndigits decimal points
|
||||
:param rt_str: true to return string
|
||||
:return: rounded float or strings
|
||||
"""
|
||||
d = Decimal(v)
|
||||
v_str = ("{0:.%sf}" % ndigits).format(round(d, ndigits))
|
||||
if rt_str:
|
||||
@ -84,8 +98,19 @@ def round_float(v, ndigits=2, rt_str=False):
|
||||
|
||||
|
||||
def draw_keypoints(vis, keypoints, color=(0, 0, 255)):
|
||||
"""
|
||||
draws a point on cv2 image array
|
||||
:param vis: cv2 image array to draw
|
||||
:param keypoints: keypoints array to draw
|
||||
:param color: color of the point
|
||||
"""
|
||||
for kp in keypoints:
|
||||
x, y = kp.pt
|
||||
cv2.circle(vis, (int(x), int(y)), 5, color, -1)
|
||||
|
||||
# np.set_printoptions(threshold=sys.maxsize)
|
||||
def enable_full_array_printing():
|
||||
"""
|
||||
Used to enable full array logging
|
||||
(summarized arrays are printed by default)
|
||||
"""
|
||||
np.set_printoptions(threshold=sys.maxsize)
|
||||
|
26
log.py
26
log.py
@ -2,36 +2,62 @@ from controls import *
|
||||
|
||||
|
||||
class Log:
|
||||
"""
|
||||
Simple logging script
|
||||
"""
|
||||
ouUsed = False
|
||||
prevOuUsed = False
|
||||
printIds = []
|
||||
|
||||
@staticmethod
|
||||
def Loop():
|
||||
"""
|
||||
Method to be called in the start of the main loop
|
||||
"""
|
||||
Log.ouUsed = False
|
||||
|
||||
@staticmethod
|
||||
def LoopEnd():
|
||||
"""
|
||||
method to be called in the end of the main loop
|
||||
"""
|
||||
if Log.prevOuUsed and not Log.ouUsed:
|
||||
Log.ctrl()
|
||||
Log.prevOuUsed = Log.ouUsed
|
||||
|
||||
@staticmethod
|
||||
def ctrl():
|
||||
"""
|
||||
Logs the control manual for the current control state
|
||||
"""
|
||||
print(Control.getControlHelp())
|
||||
|
||||
@staticmethod
|
||||
def ou(s):
|
||||
"""
|
||||
Logging output which is supposed to dumps alot of data,
|
||||
so that after it ends, controls help is printed again
|
||||
:param s: stirng to log
|
||||
"""
|
||||
Log.ouUsed = True
|
||||
print(s)
|
||||
|
||||
@staticmethod
|
||||
def po(id, s):
|
||||
"""
|
||||
print once
|
||||
logs the stirng passed only once event if it is called multiple times
|
||||
:param id: unique integer
|
||||
:param s: string to log
|
||||
"""
|
||||
if id not in Log.printIds:
|
||||
print(s)
|
||||
Log.printIds.append(id)
|
||||
|
||||
@staticmethod
|
||||
def clearPrintIds():
|
||||
"""
|
||||
clears print id for print once, so that it is printed again
|
||||
"""
|
||||
Log.printIds = []
|
||||
|
||||
|
19
pixel_loc.py
19
pixel_loc.py
@ -2,6 +2,16 @@ from fishing_event import *
|
||||
|
||||
|
||||
def GetKeypointFromImage(img):
|
||||
"""
|
||||
convert image int hsv
|
||||
creates a mask for brown color
|
||||
uses blob detection to find a blob in the mask
|
||||
filter the blobs to find the correct one
|
||||
|
||||
:param img: rgb image
|
||||
:return: location of the pixel which is used to detect different fishing states
|
||||
"""
|
||||
|
||||
# Setup SimpleBlobDetector parameters.
|
||||
hsvImg = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
lower = (99, 254, 100)
|
||||
@ -37,10 +47,19 @@ def GetKeypointFromImage(img):
|
||||
|
||||
|
||||
class PixelLoc:
|
||||
"""
|
||||
finds the pixel loc and store it
|
||||
"""
|
||||
|
||||
val = None
|
||||
|
||||
@staticmethod
|
||||
def config():
|
||||
"""
|
||||
Uses the game window to get an image of the game screen
|
||||
then uses `GetKeypointFromImage()` to find the ProvisionsChalutier pixel location
|
||||
:return: false if pixel loc not found
|
||||
"""
|
||||
win = Window()
|
||||
t = GetKeypointFromImage(win.getCapture())
|
||||
|
||||
|
35
window.py
35
window.py
@ -2,6 +2,9 @@ from log import *
|
||||
|
||||
|
||||
class Window:
|
||||
"""
|
||||
Records the game window, and allows to create instance to process it
|
||||
"""
|
||||
Screen = None
|
||||
windowOffset = None
|
||||
titleOffset = None
|
||||
@ -9,12 +12,22 @@ class Window:
|
||||
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():
|
||||
"""
|
||||
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)
|
||||
@ -27,6 +40,10 @@ class Window:
|
||||
|
||||
@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))
|
||||
@ -47,12 +64,19 @@ class Window:
|
||||
|
||||
@staticmethod
|
||||
def LoopEnd():
|
||||
"""
|
||||
Executed in the end of the game loop
|
||||
"""
|
||||
cv2.waitKey(25)
|
||||
|
||||
if not Window.showing:
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
def getCapture(self):
|
||||
"""
|
||||
copies the recorded screen and then pre processes its
|
||||
:return: game window image
|
||||
"""
|
||||
temp_img = Window.Screen
|
||||
|
||||
if self.color is not None:
|
||||
@ -67,12 +91,23 @@ class Window:
|
||||
return temp_img
|
||||
|
||||
def processedImage(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.getCapture()
|
||||
else:
|
||||
return func(self.getCapture())
|
||||
|
||||
def show(self, name, resize=None, func=None):
|
||||
"""
|
||||
Displays the processed image for debugging purposes
|
||||
: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
|
||||
"""
|
||||
img = self.processedImage(func)
|
||||
|
||||
if resize is not None:
|
||||
|
Loading…
Reference in New Issue
Block a user