Support list input for template_finder.search() and improve code quality (#125)

This commit is contained in:
mgleed
2021-11-30 22:40:05 +01:00
committed by GitHub
parent 18ded27113
commit 33b0b533e6
14 changed files with 177 additions and 158 deletions
+3 -9
View File
@@ -25,12 +25,6 @@ ingame_ref_x=625
ingame_ref_y=669
; some distance
skill_bar_height=67
normal_x=640
normal_y=304
nightmare_x=640
nightmare_y=349
hell_x=640
hell_y=390
save_and_exit_x=633
save_and_exit_y=317
issue_occured_ok_x=640
@@ -99,9 +93,9 @@ max_walk_dist=127
is_overburdened=11,510,313,60
save_and_exit=500,233,273,173
go_btn=533,627,213,67
normal_btn=560,280,163,47
nightmare_btn=560,327,163,47
hell_btn=560,367,163,47
normal_btn=373,187,109,31
nightmare_btn=373,218,109,31
hell_btn=373,245,109,31
gold_btn=927,491,267,100
health_globe=160,580,240,140
mana_globe=887,580,240,140
+4 -4
View File
@@ -155,10 +155,10 @@ class BeltManager:
pot_positions = []
for column, row in itertools.product(range(num_loot_columns), range(4)):
center_pos, slot_img = UiManager.get_slot_pos_and_img(self._config, img, column, row)
found = self._template_finder.search("SUPER_HEALING_POTION", slot_img, threshold=0.9)[0]
found |= self._template_finder.search("SUPER_MANA_POTION", slot_img, threshold=0.9)[0]
found |= self._template_finder.search("FULL_REJUV_POTION", slot_img, threshold=0.9)[0]
found |= self._template_finder.search("REJUV_POTION", slot_img, threshold=0.9)[0]
found = self._template_finder.search("SUPER_HEALING_POTION", slot_img, threshold=0.9).valid
found |= self._template_finder.search("SUPER_MANA_POTION", slot_img, threshold=0.9).valid
found |= self._template_finder.search("FULL_REJUV_POTION", slot_img, threshold=0.9).valid
found |= self._template_finder.search("REJUV_POTION", slot_img, threshold=0.9).valid
if found:
pot_positions.append(center_pos)
keyboard.press("shift")
+7 -5
View File
@@ -126,7 +126,9 @@ class Bot:
def on_create_game(self):
self._game_stats.log_start_game()
self._template_finder.search_and_wait("D2_LOGO_HS", roi=self._config.ui_roi["hero_selection_logo"])
template_match = self._template_finder.search_and_wait("D2_LOGO_HS", roi=self._config.ui_roi["hero_selection_logo"])
if template_match.valid:
Logger.debug(f"Found {template_match.name}")
if not self._ui_manager.start_game():
return
self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"])
@@ -199,7 +201,7 @@ class Bot:
wait(0.5)
# Check if merc needs to be revived
merc_alive, _ = self._template_finder.search("MERC", self._screen.grab(), threshold=0.9, roi=[0, 0, 200, 200])
merc_alive = self._template_finder.search("MERC", self._screen.grab(), threshold=0.9, roi=[0, 0, 200, 200]).valid
if not merc_alive:
Logger.info("Reviving merc.")
if not self._pather.traverse_nodes(self._curr_location, Location.QUAL_KEHK, self._char):
@@ -269,7 +271,7 @@ class Bot:
wait(0.2, 0.4)
self.success &= bot._char.select_by_template(["A5_RED_PORTAL", "A5_RED_PORTAL_TEXT"], expect_loading_screen=True)
time.sleep(0.5)
self.success &= bot._template_finder.search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, time_out=20)[0]
self.success &= bot._template_finder.search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, time_out=20).valid
if not self.success:
return
if not bot._pre_buffed:
@@ -304,7 +306,7 @@ class Bot:
wait(1.0)
bot._ui_manager.use_wp(4, 1)
time.sleep(0.5)
self.success = bot._template_finder.search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20)[0]
self.success = bot._template_finder.search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20).valid
if not self.success:
return
if not bot._pre_buffed:
@@ -360,7 +362,7 @@ class Bot:
self._tps_left -= 1
success &= not self._death_manager.handle_death_screen()
if success:
success, _= self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=10)
success = self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=10).valid
if success:
self._tp_is_up = True
self._curr_location = Location.A5_TOWN_START
+9 -16
View File
@@ -41,14 +41,14 @@ class IChar:
def select_by_template(self, template_type: Union[str, List[str]], expect_loading_screen: bool = False) -> bool:
if template_type == "A5_STASH":
# sometimes waypoint is opened and stash not found because of that, check for that
if self._template_finder.search("WAYPOINT_MENU", self._screen.grab())[0]:
if self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid:
keyboard.send("esc")
Logger.debug(f"Select {template_type}")
start = time.time()
while (time.time() - start) < 8:
success, screen_loc = self._template_finder.search_and_wait(template_type, time_out=2)
if success:
x_m, y_m = self._screen.convert_screen_to_monitor(screen_loc)
template_match = self._template_finder.search_and_wait(template_type, time_out=2)
if template_match.valid:
x_m, y_m = self._screen.convert_screen_to_monitor(template_match.position)
mouse.move(x_m, y_m)
wait(0.3, 0.4)
mouse.click(button="left")
@@ -113,23 +113,16 @@ class IChar:
start = time.time()
while (time.time() - start) < 8:
img = self._screen.grab()
success1, pos1 = self._template_finder.search(
"BLUE_PORTAL",
template_match = self._template_finder.search(
["BLUE_PORTAL","BLUE_PORTAL_2"],
img,
threshold=0.66,
roi=roi,
normalize_monitor=True
)
success2, pos2 = self._template_finder.search(
"BLUE_PORTAL_2",
img,
threshold=0.7,
roi=roi,
normalize_monitor=True
)
if success1 or success2:
pos = pos1 if success1 else pos2
pos = (pos[0], pos[1] + 30 )
if template_match.valid:
pos = template_match.position
pos = (pos[0], pos[1] + 30)
# Note: Template is top of portal, thus move the y-position a bit to the bottom
mouse.move(*pos, randomize=6, delay_factor=[0.9, 1.1])
wait(0.08, 0.15)
+1 -1
View File
@@ -52,7 +52,7 @@ class DeathManager:
wait(0.1, 0.2)
keyboard.release(self._config.char["show_items"])
time.sleep(6)
if self._template_finder.search("D2_LOGO_HS", self._screen.grab(), roi=self._config.ui_roi["hero_selection_logo"])[0]:
if self._template_finder.search("D2_LOGO_HS", self._screen.grab(), roi=self._config.ui_roi["hero_selection_logo"]).valid:
# in this case chicken executed and left the game, but we were still dead.
return True
keyboard.send("esc")
+3 -3
View File
@@ -24,10 +24,10 @@ class GameRecovery:
# make sure we are not on loading screen
is_loading = True
while is_loading:
is_loading = self._template_finder.search("LOADING", self._screen.grab())[0]
is_loading = self._template_finder.search("LOADING", self._screen.grab()).valid
time.sleep(0.5)
# first lets just see if you might already be at hero selection
found, _ = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=1, take_ss=False, roi=self._config.ui_roi["hero_selection_logo"])
found = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=1, take_ss=False, roi=self._config.ui_roi["hero_selection_logo"]).valid
if found:
return True
# would have been too easy, maybe we have died?
@@ -39,7 +39,7 @@ class GameRecovery:
keyboard.send("esc")
time.sleep(1)
templates = ["SAVE_AND_EXIT_NO_HIGHLIGHT","SAVE_AND_EXIT_HIGHLIGHT"]
found, _ = self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False)
found = self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False).valid
if found:
keyboard.send("esc")
time.sleep(1)
+1 -1
View File
@@ -109,7 +109,7 @@ class HealthManager:
self._belt_manager.drink_potion("mana", stats=[health_percentage, mana_percentage])
self._last_mana = time.time()
# check merc
merc_alive, _ = self._template_finder.search("MERC", img, roi=self._config.ui_roi["merc_icon"])
merc_alive = self._template_finder.search("MERC", img, roi=self._config.ui_roi["merc_icon"]).valid
if merc_alive:
merc_health_percentage = self.get_merc_health(img)
last_drink = time.time() - self._last_merc_healh
+11 -11
View File
@@ -62,9 +62,9 @@ class NpcManager:
img = self._screen.grab()
results = []
for key in self._npcs[npc_key]["template_group"]:
res, pos = self._template_finder.search(key, img, threshold=0.35, roi=roi, normalize_monitor=True)
if res:
results.append({"pos": pos, "score": self._template_finder.last_score})
res = self._template_finder.search(key, img, threshold=0.35, roi=roi, normalize_monitor=True)
if res.valid:
results.append({"pos": res.position, "score": res.score})
results = sorted(results, key=lambda r: r["score"], reverse=True)
for result in results:
@@ -73,13 +73,13 @@ class NpcManager:
wait(0.2, 0.3)
_, filtered_inp_w = color_filter(self._screen.grab(), self._config.colors["white"])
_, filtered_inp_g = color_filter(self._screen.grab(), self._config.colors["gold"])
res_w, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.9, roi=roi)
res_g, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.9, roi=roi)
res_w = self._template_finder.search(self._npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.9, roi=roi).valid
res_g = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.9, roi=roi).valid
if res_w:
mouse.click(button="left")
wait(1.4, 1.7)
_, filtered_inp = color_filter(self._screen.grab(), self._config.colors["gold"])
res, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp, 0.9, roi=roi)
res = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp, 0.9, roi=roi).valid
if res:
return True
elif res_g:
@@ -88,13 +88,13 @@ class NpcManager:
def press_npc_btn(self, npc_key: Npc, action_btn_key: str):
_, filtered_inp = color_filter(self._screen.grab(), self._config.colors["white"])
res, pos = self._template_finder.search(
self._npcs[npc_key]["action_btns"][action_btn_key],
filtered_inp, 0.85, roi=self._config.ui_roi["cut_skill_bar"],
res = self._template_finder.search(
self._npcs[npc_key]["action_btns"][action_btn_key],
filtered_inp, 0.85, roi=self._config.ui_roi["cut_skill_bar"],
normalize_monitor=True
)
if res:
mouse.move(*pos, randomize=3, delay_factor=[1.0, 1.5])
if res.valid:
mouse.move(*res.position, randomize=3, delay_factor=[1.0, 1.5])
wait(0.2, 0.4)
mouse.click(button="left")
wait(0.3, 0.4)
+8 -5
View File
@@ -137,7 +137,10 @@ class Pather:
success = True
ref_pos_screen = template_map[template_type]
else:
success, ref_pos_screen = self._template_finder.search(template_type, img)
template_match = self._template_finder.search(template_type, img)
if template_match.valid:
ref_pos_screen = template_match.position
success = True
if success:
template_map[template_type] = ref_pos_screen
# Get reference position of template in abs coordinates
@@ -201,10 +204,10 @@ class Pather:
def find_abs_node_pos(self, node_idx: int, img: np.ndarray) -> Tuple[float, float]:
node = self._nodes[node_idx]
for template_type in node:
success, ref_pos_screen = self._template_finder.search(template_type, img)
if success:
template_match = self._template_finder.search(template_type, img)
if template_match.valid:
# Get reference position of template in abs coordinates
ref_pos_abs = self._screen.convert_screen_to_abs(ref_pos_screen)
ref_pos_abs = self._screen.convert_screen_to_abs(template_match.position)
# Calc the abs node position with the relative coordinates (relative to ref)
node_pos_rel = self._get_node(node_idx, template_type)
node_pos_abs = self._convert_rel_to_abs(node_pos_rel, ref_pos_abs)
@@ -231,7 +234,7 @@ class Pather:
while not continue_to_next_node:
img = self._screen.grab()
if (time.time() - last_move) > time_out:
success, _ = self._template_finder.search("WAYPOINT_MENU", img)
success = self._template_finder.search("WAYPOINT_MENU", img).valid
if success:
# sometimes bot opens waypoint menu, close it to find templates again
Logger.debug("Opened wp, closing it again")
+8 -8
View File
@@ -78,19 +78,19 @@ class JavaShopper:
img = self._screen.grab()
# 20 IAS gloves have a unique color so we can skip all others
ias_glove_found, pos = self._template_finder.search(
ias_glove_found = self._template_finder.search(
ref=load_template(asset_folder + "ias_gloves.png", 1.0),
inp_img=img,
threshold=0.96,
roi=self.config.ui_roi["vendor_stash"],
normalize_monitor=True,
)
if ias_glove_found:
if ias_glove_found.valid:
self.ias_gloves_seen += 1
mouse.move(*pos)
mouse.move(*ias_glove_found.position)
time.sleep(0.1)
img = self._screen.grab()
gg_gloves_found, pos = self._template_finder.search(
gg_gloves_found = self._template_finder.search(
ref=load_template(
asset_folder + "gg_gloves.png", 1.0
),
@@ -98,7 +98,7 @@ class JavaShopper:
threshold=0.80,
normalize_monitor=True,
)
if gg_gloves_found:
if gg_gloves_found.valid:
mouse.click(button="right")
Logger.info("GG gloves bought!")
self.gloves_bought += 1
@@ -126,9 +126,9 @@ class JavaShopper:
def select_by_template(self, template_type: str) -> bool:
Logger.debug(f"Select {template_type}")
success, screen_loc = self._template_finder.search_and_wait(template_type, time_out=10)
if success:
x_m, y_m = self._screen.convert_screen_to_monitor(screen_loc)
template_match = self._template_finder.search_and_wait(template_type, time_out=10)
if template_match.valid:
x_m, y_m = self._screen.convert_screen_to_monitor(template_match.position)
mouse.move(x_m, y_m)
wait(0.1, 0.2)
mouse.click(button="left")
+81 -47
View File
@@ -1,6 +1,7 @@
import cv2
from screen import Screen
from typing import Tuple, Union, List
from dataclasses import dataclass
import numpy as np
from logger import Logger
import time
@@ -9,13 +10,19 @@ from config import Config
from utils.misc import load_template
@dataclass
class TemplateMatch:
name: str = None
score: float = -1.0
position: Tuple[float, float] = None
valid: bool = False
class TemplateFinder:
def __init__(self, screen: Screen, scale_factor: float = None):
"""
:param screen: Screen object
:param scale_factor: Scale factor that is used for templates. Note: UI and NPC templates will always have scale of 1.0
"""
self.last_score = -1.0
self._screen = screen
self._config = Config()
self._scale_factor = 1.0
@@ -138,18 +145,20 @@ class TemplateFinder:
def search(
self,
ref: Union[str, np.ndarray],
ref: Union[str, np.ndarray, List[str]],
inp_img: np.ndarray,
threshold: float = None,
roi: List[float] = None,
normalize_monitor: bool = False,
) -> Tuple[bool, Tuple[float, float]]:
best_match: bool = False
) -> TemplateMatch:
"""
Search for a template in an image
:param ref: Either key of a already loaded template or a image which is used as template
:param ref: Either key of a already loaded template, list of such keys, or a image which is used as template
:param inp_img: Image in which the template will be searched
:param threshold: Threshold which determines if a template is found or not
:param roi: Region of Interest of the inp_img to restrict search area. Format [left, top, width, height]
:param best_match: If list input, will search for list of templates by best match. Default behavior is first match.
:return: Returns found flag and the position as [bool, [x, y]]. If not found, position will be None. Position in image space.
"""
threshold = self._config.advanced_options["template_threshold"] if threshold is None else threshold
@@ -160,31 +169,61 @@ class TemplateFinder:
inp_img = inp_img[ry:ry + rh, rx:rx + rw]
if type(ref) == str:
template = self._templates[ref][0]
scale = self._templates[ref][1]
templates = [self._templates[ref][0]]
scales = [self._templates[ref][1]]
names = [ref]
best_match = False
elif type(ref) == list:
templates = [self._templates[i][0] for i in ref]
scales = [self._templates[i][1] for i in ref]
names = ref
else:
template = ref
scale = 1.0
templates = [ref]
scales = [1.0]
best_match = False
img: np.ndarray = cv2.resize(inp_img, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
rx *= scale
ry *= scale
rw *= scale
rh *= scale
scores = [0] * len(ref)
ref_points = [(0, 0)] * len(ref)
for count, template in enumerate(templates):
template_match = TemplateMatch()
scale = scales[count]
if img.shape[0] > template.shape[0] and img.shape[1] > template.shape[1]:
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_pos = cv2.minMaxLoc(res)
self.last_score = max_val
if max_val > threshold:
ref_point = (max_pos[0] + int(template.shape[1] * 0.5) + rx, max_pos[1] + int(template.shape[0] * 0.5) + ry)
ref_point = (int(ref_point[0] * (1.0 / scale)), int(ref_point[1] * (1.0 / scale)))
img: np.ndarray = cv2.resize(inp_img, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
rx *= scale
ry *= scale
rw *= scale
rh *= scale
if normalize_monitor:
ref_point = self._screen.convert_screen_to_monitor(ref_point)
if img.shape[0] > template.shape[0] and img.shape[1] > template.shape[1]:
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_pos = cv2.minMaxLoc(res)
if max_val > threshold:
ref_point = (max_pos[0] + int(template.shape[1] * 0.5) + rx, max_pos[1] + int(template.shape[0] * 0.5) + ry)
ref_point = (int(ref_point[0] * (1.0 / scale)), int(ref_point[1] * (1.0 / scale)))
return True, ref_point
return False, None
if normalize_monitor:
ref_point = self._screen.convert_screen_to_monitor(ref_point)
if best_match:
scores[count]=max_val
ref_points[count]=ref_point
else:
try: template_match.name = names[count]
except: pass
template_match.position = ref_point
template_match.score = max_val
template_match.valid = True
return template_match
if max(scores) > 0:
idx=scores.index(max(scores))
try: template_match.name = names[idx]
except: pass
template_match.position = ref_points[idx]
template_match.score = scores[idx]
template_match.valid = True
return template_match
def search_and_wait(
self,
@@ -192,36 +231,34 @@ class TemplateFinder:
roi: List[float] = None,
time_out: float = None,
threshold: float = None,
best_match: bool = False,
take_ss: bool = True
) -> Tuple[bool, Tuple[float, float]]:
) -> TemplateMatch:
"""
Helper function that will loop and keep searching for a template
:param ref: Key of template which has been loaded beforehand
:param ref: Key of template (or list of keys) which has been loaded beforehand
:param time_out: After this amount of time the search will stop and it will return [False, None]
:param threshold: Adapt threshold for being found
:param best_match: If list input, will search for list of templates by best match. Default behavior is first match.
:param take_ss: Bool value to take screenshot on timeout or not (flag must still be set in params!)
Rest of params same as TemplateFinder.search()
"""
if type(ref) is str:
ref = [ref]
threshold = self._config.advanced_options["template_threshold"] if threshold is None else threshold
Logger.debug(f"Waiting for Template {ref}")
start = time.time()
while 1:
img = self._screen.grab()
template_match = self.search(ref, img, roi=roi, threshold=threshold, best_match=best_match)
is_loading_black_roi = np.average(img[:, 0:self._config.ui_roi["loading_left_black"][2]]) < 1.0
if type(ref) is str:
ref = [ref]
for x in ref:
success, pos = self.search(x, img, roi=roi, threshold=threshold)
if success:
break
if not is_loading_black_roi:
if success:
return True, pos
elif time_out is not None and (time.time() - start) > time_out:
if not is_loading_black_roi or "LOADING" in ref:
if template_match.valid:
return template_match
if time_out is not None and (time.time() - start) > time_out:
if self._config.general["info_screenshots"] and take_ss:
cv2.imwrite(f"./info_screenshots/info_wait_for_{ref}_time_out_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
return False, None
return template_match
# Testing: Have whatever you want to find on the screen
@@ -231,19 +268,16 @@ if __name__ == "__main__":
config = Config()
screen = Screen(config.general["monitor"])
template_finder = TemplateFinder(screen)
search_templates = ["ELDRITCH_4", "ELDRITCH_3", "ELDRITCH_2", "ELDRITCH_1"]
scores = {}
search_templates = ["ELDRITCH_1", "ELDRITCH_4", "ELDRITCH_3", "ELDRITCH_2"]
while 1:
# img = cv2.imread("")
img = screen.grab()
display_img = img.copy()
for template_name in search_templates:
success, pos = template_finder.search(template_name, img)
scores[template_name] = template_finder.last_score
if success:
cv2.putText(display_img, str(template_name), pos, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
cv2.circle(display_img, pos, 7, (255, 0, 0), thickness=5)
template_match = template_finder.search(search_templates,img,best_match=1)
if template_match.valid:
cv2.putText(display_img, str(template_match.name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
cv2.circle(display_img, template_match.position, 7, (255, 0, 0), thickness=5)
print(f"Name: {template_match.name} Pos: {template_match.position}, Score: {template_match.score}")
display_img = cv2.resize(display_img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST)
print(scores)
cv2.imshow('test', display_img)
key = cv2.waitKey(1)
+33 -40
View File
@@ -67,7 +67,7 @@ class UiManager():
self._config.ui_pos["skill_height"]
]
for template in template_list:
if self._template_finder.search(template, self._screen.grab(), threshold=0.94, roi=roi)[0]:
if self._template_finder.search(template, self._screen.grab(), threshold=0.94, roi=roi).valid:
return True
return False
@@ -109,7 +109,7 @@ class UiManager():
# TODO: Add hardcoded coordinates to ini file
away_x_m, away_y_m = self._screen.convert_abs_to_monitor((-167, 0))
templates = ["SAVE_AND_EXIT_NO_HIGHLIGHT","SAVE_AND_EXIT_HIGHLIGHT"]
while self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False)[0]:
while self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False).valid:
delay = [0.9, 1.1]
if does_chicken:
delay = [0.3, 0.4]
@@ -133,6 +133,7 @@ class UiManager():
Starting a game. Will wait and retry on server connection issue.
:return: Bool if action was successful
"""
Logger.debug("Wait for active Play button")
# To test the start_game() function seperatly, just run:
# (botty) >> python src/ui_manager.py
# then go to D2r window -> press "f11", you can exit with "f12"
@@ -143,16 +144,10 @@ class UiManager():
# it returns a bool value (True or False) if the button was found, and the position of it
# roi = Region of interest. It reduces the search area and can be adapted within game.ini
# by running >> python src/screen.py you can visualize all of the currently set region of interests
found_btn, btn_pos = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["go_btn"], threshold=0.8)
score_enabled = self._template_finder.last_score
# same as above, just with different template
self._template_finder.search("PLAY_BTN_GRAY", img, roi=self._config.ui_roi["go_btn"], threshold=0.8)
score_disabled = self._template_finder.last_score
# found_btn is a bool (True or False), if a play btn was found and it is not grayed out, then we can proceed
found_btn = found_btn and score_enabled > score_disabled
if found_btn:
found_btn = self._template_finder.search(["PLAY_BTN","PLAY_BTN_GRAY"], img, roi=self._config.ui_roi["go_btn"], threshold=0.8, best_match=True)
if found_btn.name == "PLAY_BTN":
# We need to convert the position to monitor coordinates (e.g. if someone is using 2 monitors or windowed mode)
x, y = self._screen.convert_screen_to_monitor(btn_pos)
x, y = self._screen.convert_screen_to_monitor(found_btn.position)
Logger.debug(f"Found Play Btn")
# move the mouse to the play button and randomize the position a bit. +-35 pixel in x direction, +-7 pixel in y direction
mouse.move(x, y, randomize=[35, 7], delay_factor=[1.0, 1.8])
@@ -162,28 +157,22 @@ class UiManager():
break
else:
# Might be in online mode?
found_btn, _ = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["play_btn"], threshold=0.8)
if found_btn:
found_btn = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["play_btn"], threshold=0.8)
if found_btn.valid:
Logger.error("Botty only works for single player. Please switch to offline mode and restart botty!")
return False
time.sleep(3.0)
difficulty=self._config.general["difficulty"].lower()
Logger.debug(f"Searching for {difficulty} Btn...")
difficulty=self._config.general["difficulty"].upper()
while 1:
# edge case: if a player hasn't unlocked nightmare difficulty, there won't be an option to select difficulty after clicking play button
wait(0.75,1.25)
if self._template_finder.search("LOADING", self._screen.grab())[0]:
Logger.debug("On loading screen, nightmare not unlocked")
return True
found, pos = self._template_finder.search_and_wait("NORMAL_BTN", roi=self._config.ui_roi["normal_btn"], time_out=8)
if not found:
Logger.debug("Could not find btn, try from start again")
template_match = self._template_finder.search_and_wait(["LOADING", f"{difficulty}_BTN"], time_out=8, threshold=0.9)
if not template_match.valid:
Logger.debug(f"Could not find, try from start again")
return self.start_game()
x, y = self._screen.convert_screen_to_monitor((self._config.ui_pos[f"{difficulty}_x"], self._config.ui_pos[f"{difficulty}_y"]))
if template_match.name == "LOADING":
Logger.debug(f"Found {template_match.name} screen")
return True
x, y = self._screen.convert_screen_to_monitor(template_match.position)
Logger.debug(f"Found {difficulty} Btn -> clicking it")
mouse.move(x, y, randomize=[50, 9], delay_factor=[1.0, 1.8])
wait(0.15, 0.2)
@@ -192,7 +181,7 @@ class UiManager():
# check for server issue
wait(2.0)
server_issue, _ = self._template_finder.search("SERVER_ISSUES", self._screen.grab())
server_issue = self._template_finder.search("SERVER_ISSUES", self._screen.grab()).valid
if server_issue:
Logger.warning("Server connection issue. waiting 20s")
x, y = self._screen.convert_screen_to_monitor((self._config.ui_pos["issue_occured_ok_x"], self._config.ui_pos["issue_occured_ok_y"]))
@@ -276,8 +265,8 @@ class UiManager():
# TODO: Do not stash portal scrolls and potions but throw them out of inventory on the ground!
# then the pickit check for potions and belt free can also be removed
Logger.debug("Searching for inventory gold btn...")
found, pos_gold_btn = self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=self._config.ui_roi["gold_btn"], time_out=20)
if not found:
gold_btn = self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=self._config.ui_roi["gold_btn"], time_out=20)
if not gold_btn.valid:
Logger.error("Could not determine to be in stash menu. Continue...")
return
Logger.debug("Found inventory gold btn")
@@ -292,7 +281,7 @@ class UiManager():
wait(0.3, 0.4)
# stash gold
if self._config.char["stash_gold"]:
x, y = self._screen.convert_screen_to_monitor(pos_gold_btn)
x, y = self._screen.convert_screen_to_monitor(gold_btn.position)
mouse.move(x, y, randomize=4)
wait(0.1, 0.15)
mouse.press(button="left")
@@ -394,10 +383,10 @@ class UiManager():
Repair and fills up TP buy selling tomb and buying. Vendor inventory needs to be open!
:return: Bool if success
"""
found, pos_repair_abs = self._template_finder.search_and_wait("REPAIR_BTN", roi=self._config.ui_roi["repair_btn"], time_out=4)
if not found:
repair_btn = self._template_finder.search_and_wait("REPAIR_BTN", roi=self._config.ui_roi["repair_btn"], time_out=4)
if not repair_btn.valid:
return False
x, y = self._screen.convert_screen_to_monitor(pos_repair_abs)
x, y = self._screen.convert_screen_to_monitor(repair_btn.position)
mouse.move(x, y, randomize=12, delay_factor=[1.0, 1.5])
wait(0.1, 0.15)
mouse.click(button="left")
@@ -407,10 +396,10 @@ class UiManager():
wait(0.1, 0.15)
mouse.click(button="left")
wait(0.5, 0.6)
found, pos_tp_inventory = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["inventory"], time_out=3)
if not found:
tp_tomb = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["inventory"], time_out=3)
if not tp_tomb.valid:
return False
x, y = self._screen.convert_screen_to_monitor(pos_tp_inventory)
x, y = self._screen.convert_screen_to_monitor(tp_tomb.position)
keyboard.send('ctrl', do_release=False)
mouse.move(x, y, randomize=8, delay_factor=[1.0, 1.5])
wait(0.1, 0.15)
@@ -419,16 +408,20 @@ class UiManager():
mouse.release(button="left")
wait(0.5, 0.6)
keyboard.send('ctrl', do_press=False)
found, pos_tp_inventory = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["vendor_stash"], time_out=3)
if not found:
tp_tomb = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["vendor_stash"], time_out=3)
if not tp_tomb.valid:
return False
x, y = self._screen.convert_screen_to_monitor(pos_tp_inventory)
x, y = self._screen.convert_screen_to_monitor(tp_tomb.position)
keyboard.send('ctrl', do_release=False)
mouse.move(x, y, randomize=8, delay_factor=[1.0, 1.5])
wait(0.1, 0.15)
mouse.click(button="right")
wait(0.1, 0.15)
keyboard.send('ctrl', do_press=False)
# delay to make sure the tome has time to transfer to other inventory before closing window
tp_tomb = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["inventory"], time_out=3)
if not tp_tomb.valid:
return False
return True
+5 -5
View File
@@ -29,11 +29,11 @@ def run_graphic_debugger():
# Show Town A5 template matches
scores = {}
for template_name in search_templates:
success, pos = template_finder.search(template_name, img, threshold=0.65)
if success:
scores[template_name] = template_finder.last_score
cv2.putText(combined_img, str(template_name), pos, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
cv2.circle(combined_img, pos, 7, (255, 0, 0), thickness=5)
template_match = template_finder.search(template_name, img, threshold=0.65)
if template_match.valid:
scores[template_match.name] = template_match.score
cv2.putText(combined_img, str(template_name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
cv2.circle(combined_img, template_match.position, 7, (255, 0, 0), thickness=5)
if len(scores) > 0:
print(scores)
# Show img
+3 -3
View File
@@ -33,10 +33,10 @@ class NodeRecorder:
img = self._screen.grab()
for key in self._template_finder._templates:
if key not in self._ref_points:
found, pos = self._template_finder.search(key, img)
if found:
found = self._template_finder.search(key, img)
if found.valid:
print(f"Found Previous: {key}")
self._ref_points[key] = pos
self._ref_points[key] = found.position
def hook(self, e):
if e.event_type == "down":