diff --git a/src/char/BaseChar.py b/src/char/BaseChar.py index d29c0db..38e576c 100644 --- a/src/char/BaseChar.py +++ b/src/char/BaseChar.py @@ -1,10 +1,6 @@ import time -import cv2 -import numpy as np - from enum import IntEnum, StrEnum -from ok.color.Color import color_range_to_bound from ok.config.Config import Config from ok.logging.Logger import get_logger from src import text_white_color @@ -357,63 +353,11 @@ class BaseChar: return time.time() - self.last_echo > self.echo_cd def is_con_full(self): - return self.get_current_con() == 1 + return self.task.is_con_full() def get_current_con(self): - box = self.task.box_of_screen_scaled(3840, 2160, 1422, 1939, to_x=1566, to_y=2076, name='con_full', - hcenter=True) - box.confidence = 0 - - max_area = 0 - percent = 0 - max_is_full = False - color_index = -1 - target_index = self.config.get('_ring_color_index', -1) - cropped = box.crop_frame(self.task.frame) - for i in range(len(con_colors)): - if target_index != -1 and i != target_index: - continue - color_range = con_colors[i] - area, is_full = self.count_rings(cropped, color_range, - 1500 / 3840 / 2160 * self.task.screen_width * self.task.screen_height) - # self.logger.debug(f'is_con_full test color_range {color_range} {area, is_full}') - if is_full: - max_is_full = is_full - color_index = i - if area > max_area: - max_area = int(area) - if max_is_full: - self.logger.info( - f'is_con_full found a full ring {self.config.get("_full_ring_area", 0)} -> {max_area} {color_index}') - self.config['_full_ring_area'] = max_area - self.config['_ring_color_index'] = color_index - self.logger.info( - f'is_con_full2 found a full ring {self.config.get("_full_ring_area", 0)} -> {max_area} {color_index}') - if self.config.get('_full_ring_area', 0) > 0: - percent = max_area / self.config['_full_ring_area'] - if not max_is_full and percent >= 1: - self.logger.warning( - f'is_con_full not full but percent greater than 1, set to 0.99, {percent} {max_is_full}') - # self.task.screenshot( - # f'is_con_full not full but percent greater than 1, set to 0.99, {percent} {max_is_full}', - # cropped) - percent = 0.99 - if percent > 1: - self.logger.error(f'is_con_full percent greater than 1, set to 1, {percent} {max_is_full}') - self.task.screenshot(f'is_con_full percent greater than 1, set to 1, {percent} {max_is_full}', cropped) - percent = 1 - # self.logger.info( - # f'is_con_full {self} {percent} {max_area}/{self.config.get("_full_ring_area", 0)} {color_index} ') - # if self.task.debug: - # self.task.screenshot( - # f'is_con_full {self} {percent} {max_area}/{self.config.get("_full_ring_area", 0)} {color_index} ', - # cropped) - box.confidence = percent - self.current_con = percent - self.task.draw_boxes(f'is_con_full_{self}', box) - if percent > 1: - percent = 1 - return percent + self.current_con = self.task.get_current_con() + return self.current_con def is_forte_full(self): box = self.task.box_of_screen_scaled(3840, 2160, 2251, 1993, 2311, 2016, name='forte_full', hcenter=True) @@ -443,17 +387,6 @@ class BaseChar: return False else: return self.is_available(snap, 'liberation') - # else: - # mark_to_check = char_lib_check_marks[self.index] - # box = self.task.get_box_by_name(mark_to_check) - # box = box.copy(x_offset=-box.width, y_offset=-box.height, width_offset=box.width * 2, - # height_offset=box.height * 2) - # for match in char_lib_check_marks: - # mark = self.task.find_one(match, box=box, canny_lower=10, canny_higher=80, threshold=0.8) - # if mark is not None: - # self.logger.debug(f'{self.__repr__()} liberation ready by checking mark {mark}') - # self.liberation_available_mark = True - # return True def __str__(self): return self.__repr__() @@ -495,77 +428,6 @@ class BaseChar: def flying(self): return self.current_resonance() == 0 - def count_rings(self, image, color_range, min_area): - # Define the color range - lower_bound, upper_bound = color_range_to_bound(color_range) - - image_with_contours = image.copy() - - # Create a binary mask - mask = cv2.inRange(image, lower_bound, upper_bound) - - # Find connected components - num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8) - - colors = [ - (0, 255, 0), # Green - (0, 0, 255), # Red - (255, 0, 0), # Blue - (0, 255, 255), # Yellow - (255, 0, 255), # Magenta - (255, 255, 0) # Cyan - ] - - # Function to check if a component forms a ring - def is_full_ring(component_mask): - # Find contours - contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - if len(contours) != 1: - return False - contour = contours[0] - - # Check if the contour is closed by checking if the start and end points are the same - # if cv2.arcLength(contour, True) > 0: - # return True - # Approximate the contour with polygons. - epsilon = 0.05 * cv2.arcLength(contour, True) - approx = cv2.approxPolyDP(contour, epsilon, True) - - # Check if the polygon is closed (has no gaps) and has a reasonable number of vertices for a ring. - if not cv2.isContourConvex(approx) or len(approx) < 4: - return False - - # All conditions met, likely a close ring. - return True - - # Iterate over each component - ring_count = 0 - is_full = False - the_area = 0 - for label in range(1, num_labels): - x, y, width, height, area = stats[label, :5] - bounding_box_area = width * height - component_mask = (labels == label).astype(np.uint8) * 255 - contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - color = colors[label % len(colors)] - cv2.drawContours(image_with_contours, contours, -1, color, 2) - if bounding_box_area >= min_area: - # Select a color from the list based on the label index - if is_full_ring(component_mask): - is_full = True - the_area = area - ring_count += 1 - - # if self.task.debug: - # Save or display the image with contours - # cv2.imwrite(f'test\\test_{self}_{is_full}_{the_area}_{lower_bound}.jpg', image_with_contours) - if ring_count > 1: - is_full = False - the_area = 0 - self.logger.warning(f'is_con_full found multiple rings {ring_count}') - - return the_area, is_full - forte_white_color = { 'r': (244, 255), # Red range @@ -578,36 +440,3 @@ dot_color = { 'g': (195, 255), # Green range 'b': (195, 255) # Blue range } - -con_colors = [ - { - 'r': (205, 235), - 'g': (190, 222), # for yellow spectro - 'b': (90, 130) - }, - { - 'r': (150, 190), # Red range - 'g': (95, 140), # Green range for purple electric - 'b': (210, 249) # Blue range - }, - { - 'r': (200, 230), # Red range - 'g': (100, 130), # Green range for red fire - 'b': (75, 105) # Blue range - }, - { - 'r': (60, 95), # Red range - 'g': (150, 180), # Green range for blue ice - 'b': (210, 245) # Blue range - }, - { - 'r': (70, 110), # Red range - 'g': (215, 250), # Green range for green wind - 'b': (155, 190) # Blue range - }, - { - 'r': (190, 220), # Red range - 'g': (65, 105), # Green range for havoc - 'b': (145, 175) # Blue range - } -] diff --git a/src/combat/CombatCheck.py b/src/combat/CombatCheck.py index 8804b9f..f7c4222 100644 --- a/src/combat/CombatCheck.py +++ b/src/combat/CombatCheck.py @@ -44,16 +44,15 @@ class CombatCheck: if is_pure_black(self.frame): logger.error('getting a pure black frame for unknown reason, reset_to_false return true') return True - if recheck and time.time() - self.last_out_of_combat_time > 2.1: + if recheck: logger.info('out of combat start double check') if self.debug: self.screenshot('out of combat start double check') - self.last_out_of_combat_time = time.time() - return True - else: - self.out_of_combat_reason = reason - self.do_reset_to_false() - return False + if self.wait_until(self.check_health_bar, time_out=1.2, wait_until_before_delay=0): + return True + self.out_of_combat_reason = reason + self.do_reset_to_false() + return False def do_reset_to_false(self): self._in_combat = False @@ -102,6 +101,8 @@ class CombatCheck: if self.boss_lv_box is not None: current = self.boss_lv_box.crop_frame(self.frame) else: + self.boss_lv_template = None + self.boss_lv_box = None current = None max_val = 0 if current is not None: @@ -160,22 +161,21 @@ class CombatCheck: if now - self.last_combat_check > self.combat_check_interval: self.last_combat_check = now if check_team and not self.in_team()[0]: + logger.info('not in team break out of combat') return self.reset_to_false(recheck=False, reason="not in team") if self.check_count_down(): return True if self.boss_lv_template is not None: - if self.wait_until(self.check_boss, time_out=2, wait_until_before_delay=0): + if self.check_boss(): return True - else: - return self.reset_to_false(recheck=False, reason="boss disappear") if self.check_health_bar(): return True if self.ocr_lv_text(): return True if self.target_enemy(): return True - logger.error('target_enemy failed, break out of combat') - return self.reset_to_false(reason='target enemy failed') + logger.error('target_enemy failed, try recheck break out of combat') + return self.reset_to_false(recheck=True, reason='target enemy failed') else: return True else: diff --git a/src/task/BaseCombatTask.py b/src/task/BaseCombatTask.py index 1c569a5..5dbff41 100644 --- a/src/task/BaseCombatTask.py +++ b/src/task/BaseCombatTask.py @@ -1,10 +1,12 @@ import math import time +import cv2 +import numpy as np import win32api import re -from ok.color.Color import get_connected_area_by_color +from ok.color.Color import get_connected_area_by_color, color_range_to_bound from ok.config.ConfigOption import ConfigOption from ok.feature.FindFeature import FindFeature from ok.logging.Logger import get_logger @@ -347,6 +349,63 @@ class BaseCombatTask(BaseWWTask, FindFeature, OCR, CombatCheck): def get_resonance_percentage(self): return self.calculate_color_percentage(white_color, self.get_box_by_name('box_resonance')) + def is_con_full(self): + return self.get_current_con() == 1 + + def get_current_con(self): + box = self.box_of_screen_scaled(3840, 2160, 1422, 1939, to_x=1566, to_y=2076, name='con_full', + hcenter=True) + box.confidence = 0 + + max_area = 0 + percent = 0 + max_is_full = False + color_index = -1 + target_index = self.config.get('_ring_color_index', -1) + cropped = box.crop_frame(self.frame) + for i in range(len(con_colors)): + if target_index != -1 and i != target_index: + continue + color_range = con_colors[i] + area, is_full = self.count_rings(cropped, color_range, + 1500 / 3840 / 2160 * self.screen_width * self.screen_height) + # self.logger.debug(f'is_con_full test color_range {color_range} {area, is_full}') + if is_full: + max_is_full = is_full + color_index = i + if area > max_area: + max_area = int(area) + if max_is_full: + self.logger.info( + f'is_con_full found a full ring {self.config.get("_full_ring_area", 0)} -> {max_area} {color_index}') + self.config['_full_ring_area'] = max_area + self.config['_ring_color_index'] = color_index + self.logger.info( + f'is_con_full2 found a full ring {self.config.get("_full_ring_area", 0)} -> {max_area} {color_index}') + if self.config.get('_full_ring_area', 0) > 0: + percent = max_area / self.config['_full_ring_area'] + if not max_is_full and percent >= 1: + self.logger.warning( + f'is_con_full not full but percent greater than 1, set to 0.99, {percent} {max_is_full}') + # self.task.screenshot( + # f'is_con_full not full but percent greater than 1, set to 0.99, {percent} {max_is_full}', + # cropped) + percent = 0.99 + if percent > 1: + self.logger.error(f'is_con_full percent greater than 1, set to 1, {percent} {max_is_full}') + percent = 1 + # self.logger.info( + # f'is_con_full {self} {percent} {max_area}/{self.config.get("_full_ring_area", 0)} {color_index} ') + # if self.task.debug: + # self.task.screenshot( + # f'is_con_full {self} {percent} {max_area}/{self.config.get("_full_ring_area", 0)} {color_index} ', + # cropped) + box.confidence = percent + self.draw_boxes(f'is_con_full_{self}', box) + if percent > 1: + percent = 1 + return percent + def in_team(self): start = time.time() c1 = self.find_one('char_1_text', @@ -370,6 +429,8 @@ class BaseCombatTask(BaseWWTask, FindFeature, OCR, CombatCheck): else: return False, -1, exist_count + 1 + # Function to check if a component forms a ring + def mouse_reset(self): # logger.debug("mouse_reset") try: @@ -392,9 +453,112 @@ class BaseCombatTask(BaseWWTask, FindFeature, OCR, CombatCheck): except Exception as e: logger.error('mouse_reset exception', e) + def count_rings(self, image, color_range, min_area): + # Define the color range + lower_bound, upper_bound = color_range_to_bound(color_range) + + image_with_contours = image.copy() + + # Create a binary mask + mask = cv2.inRange(image, lower_bound, upper_bound) + + # Find connected components + num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8) + + colors = [ + (0, 255, 0), # Green + (0, 0, 255), # Red + (255, 0, 0), # Blue + (0, 255, 255), # Yellow + (255, 0, 255), # Magenta + (255, 255, 0) # Cyan + ] + + # Function to check if a component forms a ring + def is_full_ring(component_mask): + # Find contours + contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + if len(contours) != 1: + return False + contour = contours[0] + + # Check if the contour is closed by checking if the start and end points are the same + # if cv2.arcLength(contour, True) > 0: + # return True + # Approximate the contour with polygons. + epsilon = 0.05 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + + # Check if the polygon is closed (has no gaps) and has a reasonable number of vertices for a ring. + if not cv2.isContourConvex(approx) or len(approx) < 4: + return False + + # All conditions met, likely a close ring. + return True + + # Iterate over each component + ring_count = 0 + is_full = False + the_area = 0 + for label in range(1, num_labels): + x, y, width, height, area = stats[label, :5] + bounding_box_area = width * height + component_mask = (labels == label).astype(np.uint8) * 255 + contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + color = colors[label % len(colors)] + cv2.drawContours(image_with_contours, contours, -1, color, 2) + if bounding_box_area >= min_area: + # Select a color from the list based on the label index + if is_full_ring(component_mask): + is_full = True + the_area = area + ring_count += 1 + + # Save or display the image with contours + # cv2.imwrite(f'test\\test_{self}_{is_full}_{the_area}_{lower_bound}.jpg', image_with_contours) + if ring_count > 1: + is_full = False + the_area = 0 + self.logger.warning(f'is_con_full found multiple rings {ring_count}') + + return the_area, is_full + white_color = { 'r': (253, 255), # Red range 'g': (253, 255), # Green range 'b': (253, 255) # Blue range } + +con_colors = [ + { + 'r': (205, 235), + 'g': (190, 222), # for yellow spectro + 'b': (90, 130) + }, + { + 'r': (150, 190), # Red range + 'g': (95, 140), # Green range for purple electric + 'b': (210, 249) # Blue range + }, + { + 'r': (200, 230), # Red range + 'g': (100, 130), # Green range for red fire + 'b': (75, 105) # Blue range + }, + { + 'r': (60, 95), # Red range + 'g': (150, 180), # Green range for blue ice + 'b': (210, 245) # Blue range + }, + { + 'r': (70, 110), # Red range + 'g': (215, 250), # Green range for green wind + 'b': (155, 190) # Blue range + }, + { + 'r': (190, 220), # Red range + 'g': (65, 105), # Green range for havoc + 'b': (145, 175) # Blue range + } +] diff --git a/src/task/IllusiveRealmTask.py b/src/task/IllusiveRealmTask.py index 9e353cd..f6b71a8 100644 --- a/src/task/IllusiveRealmTask.py +++ b/src/task/IllusiveRealmTask.py @@ -35,13 +35,31 @@ class IllusiveRealmTask(BaseCombatTask): self.click() else: if self.available('liberation'): - self.send_key(self.get_liberation_key()) + self.send_key_and_wait_animation(self.get_liberation_key()) elif self.available('echo'): self.send_key(self.get_echo_key()) elif self.available('resonance'): self.send_key(self.get_resonance_key()) self.last_is_click = not self.last_is_click + def send_key_and_wait_animation(self, key, total_wait=10, animation_wait=5): + start = time.time() + animation_start = 0 + while time.time() - start < total_wait and ( + animation_start == 0 or time.time() - animation_start < animation_wait): + if self.in_realm() or self.in_team()[0]: + if animation_start > 0: + self.in_liberation = False + return + else: + self.send_key(key, interval=0.2) + else: + if animation_start == 0: + animation_start = time.time() + self.in_liberation = True + self.next_frame() + logger.info(f'send_key_and_wait_animation timed out {key}') + def run(self): while True: start = time.time()