feat: auto-login + OCR character selection
- Added bnet_name, bnet_pass, char_name config options - Launch options auto-append -bnetname/-bnetpass when credentials set - character_select.py: OCR fallback when template matching fails - Scans character list row-by-row, matches by name, clicks and scrolls
This commit is contained in:
@@ -7,6 +7,15 @@ target_tz=1
|
||||
saved_games_folder=
|
||||
level_max_steps=20
|
||||
|
||||
; Battle.net credentials (for auto-login at launch)
|
||||
; Leave empty to log in manually
|
||||
bnet_name=
|
||||
bnet_pass=
|
||||
|
||||
; Character name to auto-select from the character selection screen
|
||||
; If empty, bot relies on the saved character template from previous sessions
|
||||
char_name=
|
||||
|
||||
; messaging
|
||||
custom_loot_message_hook=
|
||||
custom_message_hook=
|
||||
|
||||
+15
-1
@@ -131,6 +131,9 @@ class Config:
|
||||
|
||||
|
||||
self.general = {
|
||||
"bnet_name": self._select_val("general", "bnet_name"),
|
||||
"bnet_pass": self._select_val("general", "bnet_pass"),
|
||||
"char_name": self._select_val("general", "char_name"),
|
||||
"saved_games_folder": self._select_val("general", "saved_games_folder"),
|
||||
"name": _default_iff(self._select_val("general", "name"), "", "botty"),
|
||||
"max_game_length_s": float(self._select_val("general", "max_game_length_s")),
|
||||
@@ -361,10 +364,21 @@ class Config:
|
||||
"hwnd_window_process": _default_iff(Config()._select_val("advanced_options", "hwnd_window_process"), ''),
|
||||
"window_client_area_offset": tuple(map(int, Config()._select_val("advanced_options", "window_client_area_offset").split(","))),
|
||||
"ocr_during_pickit": bool(int(self._select_val("advanced_options", "ocr_during_pickit"))),
|
||||
"launch_options": self._select_val("advanced_options", "launch_options").replace("<name>", only_lowercase_letters(self.general["name"].lower())),
|
||||
"launch_options": self._build_launch_options(),
|
||||
"override_capabilities": _default_iff(Config()._select_optional("advanced_options", "override_capabilities"), ""),
|
||||
}
|
||||
|
||||
def _build_launch_options(self) -> str:
|
||||
"""Build D2R launch options, appending auto-login flags when bnet credentials are set."""
|
||||
opts = self._select_val("advanced_options", "launch_options").replace(
|
||||
"<name>", only_lowercase_letters(self.general["name"].lower())
|
||||
)
|
||||
bnet_name = self.general.get("bnet_name", "")
|
||||
bnet_pass = self.general.get("bnet_pass", "")
|
||||
if bnet_name and bnet_pass:
|
||||
opts += f" -bnetname {bnet_name} -bnetpass {bnet_pass}"
|
||||
return opts
|
||||
|
||||
self.colors = {}
|
||||
for key in self.configs["game"]["parser"]["colors"]:
|
||||
self.colors[key] = np.split(np.array([int(x) for x in self._select_val("colors", key).split(",")]), 2)
|
||||
|
||||
+106
-1
@@ -131,4 +131,109 @@ def select_char() -> bool:
|
||||
scrolls_attempts += 1
|
||||
wait(0.4, 0.6)
|
||||
Logger.error(f"select_char: unable to find saved profile after {scrolls_attempts} scroll attempts")
|
||||
return False
|
||||
|
||||
# Fallback: try OCR-based name matching
|
||||
char_name = Config().general.get("char_name", "")
|
||||
if char_name:
|
||||
Logger.info(f"Template matching failed, trying OCR for character name: {char_name}")
|
||||
return _select_char_by_ocr(char_name)
|
||||
return False
|
||||
|
||||
# No template saved — try OCR-based lookup if char_name is set
|
||||
char_name = Config().general.get("char_name", "")
|
||||
if char_name:
|
||||
Logger.info(f"No template saved, selecting character '{char_name}' via OCR")
|
||||
return _select_char_by_ocr(char_name)
|
||||
|
||||
Logger.error("select_char: No character template saved and no char_name configured")
|
||||
return False
|
||||
|
||||
|
||||
def _select_char_by_ocr(char_name: str) -> bool:
|
||||
"""OCR-based character selection fallback. Scans visible character list, finds
|
||||
the matching name, clicks it, then scrolls and retries up to 2 times."""
|
||||
char_name_lower = char_name.lower()
|
||||
|
||||
# Ensure we're on the right tab (online vs offline)
|
||||
online_status = None
|
||||
if online_character is not None:
|
||||
img = grab()
|
||||
if (match := detect_screen_object(ScreenObjects.OnlineStatus, img)).valid:
|
||||
online_status = online_active(match)
|
||||
if online_status != online_character:
|
||||
if not select_online_tab(match.region, match.center):
|
||||
Logger.error("_select_char_by_ocr: could not switch online/offline tab")
|
||||
return False
|
||||
img = grab()
|
||||
wait(1, 1.5)
|
||||
else:
|
||||
Logger.debug("_select_char_by_ocr: online status unknown, searching all tabs")
|
||||
|
||||
if online_status is not None:
|
||||
img = grab()
|
||||
else:
|
||||
img = grab()
|
||||
|
||||
# Get the character list ROI for scrolling
|
||||
char_roi = Config().ui_roi["character_select"]
|
||||
|
||||
for scroll_pass in range(3):
|
||||
if scroll_pass > 0:
|
||||
# Scroll down and re-grab
|
||||
center = roi_center(char_roi)
|
||||
center = convert_screen_to_monitor(center)
|
||||
mouse.move(*center)
|
||||
wait(0.4, 0.6)
|
||||
mouse.wheel(-14)
|
||||
wait(0.4, 0.6)
|
||||
img = grab()
|
||||
|
||||
# OCR each visible character entry
|
||||
# The character list is a vertical column — scan rows
|
||||
x, y, w, h = char_roi
|
||||
row_height = 30 # approximate height of each character entry row
|
||||
num_rows = max(1, h // row_height)
|
||||
|
||||
for row in range(num_rows):
|
||||
row_y = y + row * row_height
|
||||
row_h = min(row_height + 5, y + h - row_y)
|
||||
if row_h < 5:
|
||||
break
|
||||
|
||||
roi = [x, row_y, w, row_h]
|
||||
row_img = cut_roi(img, roi)
|
||||
|
||||
try:
|
||||
result = ocr.image_to_text(
|
||||
images=row_img,
|
||||
model="hover-eng_inconsolata_inv_th_fast",
|
||||
psm=6,
|
||||
scale=1.2,
|
||||
crop_pad=False,
|
||||
erode=False,
|
||||
invert=False,
|
||||
threshold=0,
|
||||
digits_only=False,
|
||||
fix_regexps=True,
|
||||
check_known_errors=False,
|
||||
correct_words=False,
|
||||
)
|
||||
if result and result[0].text:
|
||||
detected = result[0].text.strip().lower()
|
||||
Logger.debug(f"Row {row}: OCR detected '{detected}'")
|
||||
if char_name_lower == detected:
|
||||
click_x = x + w // 2
|
||||
click_y = row_y + row_h // 2
|
||||
click_pos = convert_screen_to_monitor((click_x, click_y))
|
||||
Logger.info(f"Found character '{char_name}' at row {row}")
|
||||
mouse.move(*click_pos)
|
||||
wait(0.4, 0.6)
|
||||
mouse.click(button="left")
|
||||
wait(0.4, 0.6)
|
||||
return True
|
||||
except Exception as e:
|
||||
Logger.debug(f"OCR row {row} failed: {e}")
|
||||
continue
|
||||
|
||||
Logger.error(f"_select_char_by_ocr: could not find '{char_name}' after scanning all visible entries")
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user