Skip to content

Commit

Permalink
out and in dialogue, better recognition, fullscreen fix
Browse files Browse the repository at this point in the history
  • Loading branch information
tmarenko committed Jul 29, 2021
1 parent 7b86767 commit b0d8a5e
Showing 1 changed file with 53 additions and 16 deletions.
69 changes: 53 additions & 16 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time
import cv2
from tesseract3 import TesseractPool, AUTOMATIC_PAGE_SEGMENTATION
from string import ascii_letters


PW_RENDERFULLCONTENT = 2 # Properly capture DirectComposition window contents. Available from Windows 8.1
Expand All @@ -32,14 +33,46 @@ def get_text_from_image(image_gray, whitelist):
return tesseract.image_to_string(image_gray, whitelist=whitelist, page_segmentation=AUTOMATIC_PAGE_SEGMENTATION)


def is_strings_similar(original, compare, overlap=0.4):
"""Check if strings are similar.
:param original: original string.
:param compare: string to compare.
:param overlap: overlap parameter. If string's similarity >= overlap then strings are similar.
:return: True or False.
"""
def levenshtein_distance(a, b):
"""Return the Levenshtein edit distance between two strings."""
if a == b:
return 0
if len(a) < len(b):
a, b = b, a
prev_row = range(len(b) + 1)
for i, column1 in enumerate(a):
cur_row = [i + 1]
for j, column2 in enumerate(b):
insertions = prev_row[j + 1] + 1
deletions = cur_row[j] + 1
substitutions = prev_row[j] + (column1 != column2)
cur_row.append(min(insertions, deletions, substitutions))
prev_row = cur_row
return prev_row[-1]

distance = levenshtein_distance(original.upper(), compare.upper())
non_similarity = distance / len(original) if len(original) > 0 else 1
return non_similarity <= overlap


class GenshinImpact:
"""Class for working with Genshin Impact."""

GENSHIN_IMPACT_EXE = "GenshinImpact.exe"
PAIMON_NAME_COLOR_RANGE = ((235, 185, 0), (255, 205, 10))
PAIMON_NAME_COLOR_RANGE = ((230, 170, 0), (255, 210, 10))
PAIMON_DEFAULT_DIALOGUE = (0.4631496915663028, 0.7872131016037641, 0.536658108769003, 0.8286028539255994)
PAIMON_OUT_DIALOGUE = (0.4645968857034299, 0.7582338162046451, 0.5333868470215386, 0.8020998784944536)
PAIMON_NAME = "Paimon"
PAIMON_NAME_CHARACTERS = "".join(set(PAIMON_NAME))
DIALOGUE_NAME_CHARACTERS = "".join(set(ascii_letters))

def __init__(self, window_name="Genshin Impact", window_class="UnityWndClass"):
self.window_name = window_name
Expand All @@ -48,14 +81,17 @@ def __init__(self, window_name="Genshin Impact", window_class="UnityWndClass"):
self.screen_locked = False
self.last_frame = None
self.window_active = False
self.position_box = None
self._find_window_if_necessary()
self.full_width = win32api.GetSystemMetrics(0)
self.full_height = win32api.GetSystemMetrics(1)

@property
def is_full_screen(self):
if self.window and self.window_active:
return win32gui.GetForegroundWindow() == self.window
foreground = win32gui.GetForegroundWindow() == self.window
top_most_style = win32gui.GetWindowLong(self.window, win32con.GWL_EXSTYLE) & win32con.WS_EX_TOPMOST
return foreground and top_most_style == win32con.WS_EX_TOPMOST

def _find_window(self, hwnd, wildcard):
"""Enumerates over windows to find Genshin Impact process and store info about it."""
Expand Down Expand Up @@ -133,29 +169,25 @@ def _get_all_screen(self):
self.last_frame = img
return img

def _get_frame(self, rect):
def _get_frame(self):
if self.is_full_screen:
box = (rect[0] * self.full_width, rect[1] * self.full_height,
rect[2] * self.full_width, rect[3] * self.full_height)
frame = self._get_all_screen()
return frame.crop(box)
return self._get_all_screen()
else:
box = (rect[0] * self.width, rect[1] * self.height,
rect[2] * self.width, rect[3] * self.height)
frame = self._get_window_image(self.width, self.height)
return frame.crop(box)
return self._get_window_image(self.width, self.height)

def _get_text_from_rect(self, rect):
def _get_text_from_rect(self, frame, rect):
"""Gets text from frame by the rectangle (to support any available resolution).
:param rect: rectangle.
:return: text from tesseract.
"""
try:
frame = self._get_frame(rect=rect)
box = (rect[0] * frame.width, rect[1] * frame.height,
rect[2] * frame.width, rect[3] * frame.height)
frame = frame.crop(box)
color_low, color_high = array(self.PAIMON_NAME_COLOR_RANGE[0]), array(self.PAIMON_NAME_COLOR_RANGE[1])
array_image = cv2.inRange(array(frame), color_low, color_high)
text = get_text_from_image(image_gray=array_image, whitelist=self.PAIMON_NAME_CHARACTERS)
text = get_text_from_image(image_gray=array_image, whitelist=self.DIALOGUE_NAME_CHARACTERS)
return text
except BaseException as err:
print(err)
Expand All @@ -171,7 +203,12 @@ def _set_mute(self, mute=False):

def _is_paimon_speaking(self):
"""Returns when Paimon's name on the screen of dialogue."""
return self._get_text_from_rect(rect=self.PAIMON_DEFAULT_DIALOGUE) == self.PAIMON_NAME
frame = self._get_frame()
in_dialogue = is_strings_similar(self._get_text_from_rect(frame=frame, rect=self.PAIMON_DEFAULT_DIALOGUE),
self.PAIMON_NAME)
out_dialogue = is_strings_similar(self._get_text_from_rect(frame=frame, rect=self.PAIMON_OUT_DIALOGUE),
self.PAIMON_NAME)
return in_dialogue or out_dialogue

def paimon_shut_up(self):
"""Check for Paimon's dialogue and shut her up."""
Expand Down

0 comments on commit b0d8a5e

Please sign in to comment.