Support list input for template_finder.search() and improve code quality (#125)
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user