code documented and version incremented for the total fish caught feature

This commit is contained in:
DESKTOP-JVKHS7I\Adam 2019-06-30 02:05:53 +05:30
parent 6a2326b818
commit 7d78347a4a
8 changed files with 226 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

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

@ -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 = []

View File

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

View File

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