Skip to content

Commit

Permalink
Merge pull request #93 from tanjeffreyz/dev
Browse files Browse the repository at this point in the history
Bug fixes and video capture performance improvements
  • Loading branch information
tanjeffreyz authored May 28, 2022
2 parents 1845abf + 0382df0 commit 124e5b9
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 84 deletions.
2 changes: 1 addition & 1 deletion resources
Submodule resources updated 1 files
+441 −0 command_books/adele.py
14 changes: 12 additions & 2 deletions src/gui/menu/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ def __init__(self, parent, **kwargs):
super().__init__(parent, 'File', **kwargs)
# parent.add_cascade(label='File', menu=self)

self.add_command(label='New Routine', command=utils.async_callback(self, File._new_routine))
self.add_command(label='Save Routine', command=utils.async_callback(self, File._save_routine))
self.add_command(
label='New Routine',
command=utils.async_callback(self, File._new_routine),
state=tk.DISABLED
)
self.add_command(
label='Save Routine',
command=utils.async_callback(self, File._save_routine),
state=tk.DISABLED
)
self.add_separator()
self.add_command(label='Load Command Book', command=utils.async_callback(self, File._load_commands))
self.add_command(
Expand All @@ -22,6 +30,8 @@ def __init__(self, parent, **kwargs):
)

def enable_routine_state(self):
self.entryconfig('New Routine', state=tk.NORMAL)
self.entryconfig('Save Routine', state=tk.NORMAL)
self.entryconfig('Load Routine', state=tk.NORMAL)

@staticmethod
Expand Down
30 changes: 21 additions & 9 deletions src/modules/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import git
import cv2
import inspect
import importlib
import traceback
from os.path import splitext, basename
from src.common import config, utils
from src.detection import detection
Expand Down Expand Up @@ -141,13 +143,15 @@ def _solve_rune(self, model):
threshold=0.9)
if rune_buff:
rune_buff_pos = min(rune_buff, key=lambda p: p[0])
target = tuple(round(rune_buff_pos[i] + config.capture.window[i])
for i in range(2))
target = (
round(rune_buff_pos[0] + config.capture.window['left']),
round(rune_buff_pos[1] + config.capture.window['top'])
)
click(target, button='right')
self.rune_active = False
break
elif len(solution) == 4:
inferences.append(solution)
self.rune_active = False

def load_commands(self, file):
"""Prompts the user to select a command module to import. Updates config's command book."""
Expand All @@ -168,7 +172,17 @@ def load_commands(self, file):
# Import the desired command book file
module_name = splitext(basename(file))[0]
target = '.'.join(['resources', 'command_books', module_name])
module = __import__(target, fromlist=[''])
try:
module = importlib.import_module(target)
module = importlib.reload(module)
except ImportError: # Display errors in the target Command Book
print(' ! Errors during compilation:\n')
for line in traceback.format_exc().split('\n'):
line = line.rstrip()
if line:
print(' ' * 4 + line)
print(f"\n ! Command book '{module_name}' was not loaded")
return

# Check if the 'step' function has been implemented
step_found = False
Expand Down Expand Up @@ -200,7 +214,7 @@ def load_commands(self, file):

if not step_found and not movement_found:
print(f" ! Error: Must either implement both 'Move' and 'Adjust' commands, "
f"or the function 'step'.")
f"or the function 'step'")
if required_found and (step_found or movement_found):
self.module_name = module_name
self.command_book = new_cb
Expand All @@ -209,11 +223,9 @@ def load_commands(self, file):
config.gui.menu.file.enable_routine_state()
config.gui.view.status.set_cb(basename(file))
config.routine.clear()
print(f" ~ Successfully loaded command book '{module_name}'.")
return True
print(f" ~ Successfully loaded command book '{module_name}'")
else:
print(f" ! Command book '{module_name}' was not loaded.")
return False
print(f" ! Command book '{module_name}' was not loaded")

def update_submodules(self, force=False):
print('\n[~] Retrieving latest submodules:')
Expand Down
143 changes: 71 additions & 72 deletions src/modules/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import cv2
import threading
import ctypes
import mss
import mss.windows
import numpy as np
from src.common import config, utils
from ctypes import wintypes
from PIL import ImageGrab
user32 = ctypes.windll.user32
user32.SetProcessDPIAware()

Expand All @@ -16,7 +17,7 @@
MINIMAP_TOP_BORDER = 5

# The thickness of the other three borders of the minimap
MINIMAP_BOTTOM_BORDER = 8
MINIMAP_BOTTOM_BORDER = 9

# Offset in pixels to adjust for windowed mode
WINDOWED_OFFSET_TOP = 36
Expand Down Expand Up @@ -50,8 +51,13 @@ def __init__(self):
self.minimap = {}
self.minimap_ratio = 1
self.minimap_sample = None
self.window = (0, 0, 1366, 768)
self.scale = 1.0
self.sct = None
self.window = {
'left': 0,
'top': 0,
'width': 1366,
'height': 768
}

self.ready = False
self.calibrated = False
Expand All @@ -67,81 +73,74 @@ def start(self):
def _main(self):
"""Constantly monitors the player's position and in-game events."""

mss.windows.CAPTUREBLT = 0
while True:
if not self.calibrated:
handle = user32.FindWindowW(None, 'MapleStory')
rect = wintypes.RECT()
user32.GetWindowRect(handle, ctypes.pointer(rect))
rect = (rect.left, rect.top, rect.right, rect.bottom)
rect = tuple(max(0, x) for x in rect)

# Preliminary window to template match minimap
self.scale = ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100
self.window = (
rect[0],
rect[1],
max(rect[2], rect[0] + MMT_WIDTH), # Make room for minimap templates
max(rect[3], rect[1] + MMT_HEIGHT)
)

# Calibrate by finding the bottom right corner of the minimap
# Calibrate screen capture
handle = user32.FindWindowW(None, 'MapleStory')
rect = wintypes.RECT()
user32.GetWindowRect(handle, ctypes.pointer(rect))
rect = (rect.left, rect.top, rect.right, rect.bottom)
rect = tuple(max(0, x) for x in rect)

self.window['left'] = rect[0]
self.window['top'] = rect[1]
self.window['width'] = max(rect[2] - rect[0], MMT_WIDTH)
self.window['height'] = max(rect[3] - rect[1], MMT_HEIGHT)

# Calibrate by finding the bottom right corner of the minimap
with mss.mss() as self.sct:
self.frame = self.screenshot()
if self.frame is None:
continue
self.frame = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR)
tl, _ = utils.single_match(self.frame, MM_TL_TEMPLATE)
_, br = utils.single_match(self.frame, MM_BR_TEMPLATE)
mm_tl = (
tl[0] + MINIMAP_BOTTOM_BORDER,
tl[1] + MINIMAP_TOP_BORDER
)
mm_br = (
max(mm_tl[0] + PT_WIDTH, br[0] - MINIMAP_BOTTOM_BORDER),
max(mm_tl[1] + PT_HEIGHT, br[1] - MINIMAP_BOTTOM_BORDER - 1)
)

# Resize window to encompass minimap if needed
self.window = (
rect[0],
rect[1],
max(rect[2], mm_br[0]),
max(rect[3], mm_br[1])
)
self.minimap_ratio = (mm_br[0] - mm_tl[0]) / (mm_br[1] - mm_tl[1])
self.minimap_sample = self.frame[mm_tl[1]:mm_br[1], mm_tl[0]:mm_br[0]]
self.calibrated = True

# Take screenshot
self.frame = self.screenshot()
if self.frame is None:
continue
self.frame = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR)

# Crop the frame to only show the minimap
minimap = self.frame[mm_tl[1]:mm_br[1], mm_tl[0]:mm_br[0]]

# Determine the player's position
player = utils.multi_match(minimap, PLAYER_TEMPLATE, threshold=0.8)
if player:
config.player_pos = utils.convert_to_relative(player[0], minimap)

# Package display information to be polled by GUI
self.minimap = {
'minimap': minimap,
'rune_active': config.bot.rune_active,
'rune_pos': config.bot.rune_pos,
'path': config.path,
'player_pos': config.player_pos
}

if not self.ready:
self.ready = True
time.sleep(0.001)
tl, _ = utils.single_match(self.frame, MM_TL_TEMPLATE)
_, br = utils.single_match(self.frame, MM_BR_TEMPLATE)
mm_tl = (
tl[0] + MINIMAP_BOTTOM_BORDER,
tl[1] + MINIMAP_TOP_BORDER
)
mm_br = (
max(mm_tl[0] + PT_WIDTH, br[0] - MINIMAP_BOTTOM_BORDER),
max(mm_tl[1] + PT_HEIGHT, br[1] - MINIMAP_BOTTOM_BORDER)
)
self.minimap_ratio = (mm_br[0] - mm_tl[0]) / (mm_br[1] - mm_tl[1])
self.minimap_sample = self.frame[mm_tl[1]:mm_br[1], mm_tl[0]:mm_br[0]]
self.calibrated = True

with mss.mss() as self.sct:
while True:
if not self.calibrated:
break

# Take screenshot
self.frame = self.screenshot()
if self.frame is None:
continue

# Crop the frame to only show the minimap
minimap = self.frame[mm_tl[1]:mm_br[1], mm_tl[0]:mm_br[0]]

# Determine the player's position
player = utils.multi_match(minimap, PLAYER_TEMPLATE, threshold=0.8)
if player:
config.player_pos = utils.convert_to_relative(player[0], minimap)

# Package display information to be polled by GUI
self.minimap = {
'minimap': minimap,
'rune_active': config.bot.rune_active,
'rune_pos': config.bot.rune_pos,
'path': config.path,
'player_pos': config.player_pos
}

if not self.ready:
self.ready = True
time.sleep(0.001)

def screenshot(self, delay=1):
try:
return np.array(ImageGrab.grab(self.window))
except OSError:
return np.array(self.sct.grab(self.window))
except mss.exception.ScreenShotError:
print(f'\n[!] Error while taking screenshot, retrying in {delay} second'
+ ('s' if delay != 1 else ''))
time.sleep(delay)

0 comments on commit 124e5b9

Please sign in to comment.