0
0
mirror of https://github.com/ok-oldking/ok-wuthering-waves.git synced 2025-04-24 08:25:16 +00:00

添加大世界刷图

This commit is contained in:
firedcto@gmail.com 2025-04-07 23:52:22 +08:00
parent 09b0ab6694
commit 12aa21eeb5
15 changed files with 667 additions and 486 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 202 KiB

BIN
assets/images/77.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/images/78.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff

View File

@ -160,8 +160,7 @@ config = {
["src.task.TacetTask", "TacetTask"],
["src.task.FarmEchoTask", "FarmEchoTask"],
["src.task.FarmWorldBossTask", "FarmWorldBossTask"],
# ["src.task.FarmMapTask", "FarmMapTask"],
# ["src.task.Farm13CTask", "Farm13CTask"],
["src.task.FarmMapTask", "FarmMapTask"],
["ok", "DiagnosisTask"],
], 'trigger_tasks': [
["src.task.AutoCombatTask", "AutoCombatTask"],

View File

@ -56,7 +56,7 @@ numpy==2.2.4
# shapely
rapidocr==2.0.6
# via -r .\requirements.in
ok-script==0.0.522
ok-script==0.0.523
# via -r .\requirements.in
omegaconf==2.3.0
# via ok-rapidocr-dml

View File

@ -28,6 +28,7 @@ class CombatCheck(BaseWWTask):
self.last_in_realm_not_combat = 0
self._last_liberation = 0
self.target_enemy_time_out = 3
self.check_pick_echo = False
@property
def in_liberation(self):
@ -110,6 +111,8 @@ class CombatCheck(BaseWWTask):
now = time.time()
if now - self.last_combat_check > self.combat_check_interval:
self.last_combat_check = now
if self.check_pick_echo:
self.incr_drop(self.pick_f())
if self.has_target():
self.last_in_realm_not_combat = 0
return True

View File

@ -1,3 +1,5 @@
import time
from qfluentwidgets import FluentIcon
from ok import FindFeature, Logger
@ -35,8 +37,12 @@ class AutoPickTask(TriggerTask, BaseWWTask, FindFeature):
def run(self):
if not self.scene.in_team(self.in_team_and_world):
return
while f := self.find_one('pick_up_f_hcenter_vcenter', box=self.f_search_box,
threshold=0.8):
start = time.time()
while time.time() - start < 1:
f = self.find_one('pick_up_f_hcenter_vcenter', box=self.f_search_box,
threshold=0.8)
if not f:
return
percent = self.calculate_color_percentage(f_white_color, f)
if percent < 0.5:
self.log_debug(f'f white color percent: {percent} wait')

View File

@ -61,8 +61,7 @@ class BaseWWTask(BaseTask):
self.log_info('zoom map to max')
self.map_zoomed = True
self.send_key('m', after_sleep=1)
for i in range(11):
self.click(0.95, 0.29, after_sleep=0.1)
self.click_relative(0.94, 0.33, after_sleep=0.5)
self.send_key('esc', after_sleep=1)
def validate(self, key, value):
@ -140,8 +139,8 @@ class BaseWWTask(BaseTask):
return None
return f
def walk_to_box(self, find_function, time_out=30, end_condition=None):
if not find_function():
def walk_to_box(self, find_function, time_out=30, end_condition=None, y_offset=0.05):
if not find_function:
self.log_info('find_function not found, break')
return False
last_direction = None
@ -168,7 +167,7 @@ class BaseWWTask(BaseTask):
continue
x, y = treasure_icon.center()
y = max(0, y - self.height_of_screen(0.05))
y = max(0, y - self.height_of_screen(y_offset))
next_direction = self.get_direction(x, y, self.width, self.height)
if next_direction != last_direction:
if last_direction:
@ -414,7 +413,7 @@ class BaseWWTask(BaseTask):
"""
# Load the ONNX model
boxes = og.my_app.yolo_detect(self.frame, threshold=threshold, label=12)
ret = sorted(boxes, key=lambda detection: abs(detection.y - self.height/2), reverse=True)
ret = sorted(boxes, key=lambda detection: detection.x, reverse=True)
return ret
def yolo_find_all(self, threshold=0.3):
@ -435,10 +434,17 @@ class BaseWWTask(BaseTask):
def pick_echo(self):
if self.find_f_with_text(target_text=self.absorb_echo_text()):
if self.debug:
self.screenshot('pick_echo')
self.send_key('f')
if not self.handle_claim_button():
return True
def pick_f(self):
if self.find_one('pick_up_f_hcenter_vcenter', box=self.f_search_box, threshold=0.8):
self.send_key('f')
return True
def yolo_find_echo(self, use_color=True, walk=True, turn=True):
max_echo_count = 0
if self.pick_echo():
@ -452,13 +458,13 @@ class BaseWWTask(BaseTask):
echos = self.find_echo()
if self.debug:
self.draw_boxes('echo', echos)
if echos:
self.screenshot('echo')
max_echo_count = max(max_echo_count, len(echos))
self.log_debug(f'max_echo_count {max_echo_count}')
if echos and echos[0].center()[1] < self.height_of_screen(0.45):
if echos:
self.log_info(f'yolo found echo {echos}')
if self.debug:
self.screenshot('echo_yolo_picked')
return self.walk_to_box(self.find_echo, time_out=15, end_condition=self.pick_echo), max_echo_count > 1
return self.walk_to_box(self.find_echo, time_out=15, end_condition=self.pick_echo, y_offset=0.2), max_echo_count > 1
if use_color:
color_percent = self.calculate_color_percentage(echo_color, front_box)
self.log_debug(f'pick_echo color_percent:{color_percent}')
@ -599,7 +605,7 @@ class BaseWWTask(BaseTask):
return 'en_US'
return 'unknown_lang'
def teleport_to_boss(self, boss_name, use_custom=False, dead=False):
def teleport_to_boss(self, boss_name, use_custom=False):
self.zoom_map()
pos = self.bosses_pos.get(boss_name)
page = pos[0]
@ -637,13 +643,8 @@ class BaseWWTask(BaseTask):
self.log_info(f'index after scrolling down {index}')
self.click_relative(0.89, 0.91)
self.sleep(1)
# 判断是否是角色死亡,需要传送复活状态
if not dead:
self.wait_click_travel(use_custom=use_custom)
else:
self.click_relative(0.92, 0.91)
self.sleep(1)
self.click_relative(0.68, 0.6)
self.wait_click_travel(use_custom=use_custom)
self.wait_in_team_and_world(time_out=120)
self.sleep(1)
@ -674,11 +675,11 @@ class BaseWWTask(BaseTask):
self.click_relative(0.74, 0.92, after_sleep=1)
if self.wait_click_feature(['confirm_btn_hcenter_vcenter', 'confirm_btn_highlight_hcenter_vcenter'],
relative_x=-1, raise_if_not_found=True,
threshold=0.7,
threshold=0.6,
time_out=4):
self.wait_click_feature(['confirm_btn_hcenter_vcenter', 'confirm_btn_highlight_hcenter_vcenter'],
relative_x=-1, raise_if_not_found=False,
threshold=0.7,
threshold=0.6,
time_out=1)
return True
elif btn := self.find_one('gray_teleport', threshold=0.7):

View File

@ -55,12 +55,12 @@ class DiagnosisTask(WWOneTimeTask, BaseCombatTask):
self.click_relative(x, y + (start - 1) * distance)
self.sleep(0.5)
self.wait_click_feature('gray_button_challenge', raise_if_not_found=True, use_gray_scale=True,
self.wait_click_feature('gray_button_challenge', raise_if_not_found=True,
click_after_delay=0.5)
self.wait_click_feature('gray_confirm_exit_button', relative_x=-1, raise_if_not_found=False,
use_gray_scale=True, time_out=3, click_after_delay=0.5, threshold=0.8)
time_out=3, click_after_delay=0.5, threshold=0.8)
self.wait_click_feature('gray_start_battle', relative_x=-1, raise_if_not_found=True,
use_gray_scale=True, click_after_delay=0.5, threshold=0.8)
click_after_delay=0.5, threshold=0.8)
echo_color = {

View File

@ -44,7 +44,7 @@ class FarmEchoTask(WWOneTimeTask, BaseCombatTask):
self.walk_until_f(time_out=10,
raise_if_not_found=True)
logger.info(f'enter success')
challenge = self.wait_feature('gray_button_challenge', raise_if_not_found=True, use_gray_scale=True)
challenge = self.wait_feature('gray_button_challenge', raise_if_not_found=True)
logger.info(f'found challenge {challenge}')
self.sleep(1)
self.choose_level(self.config.get("Level"))
@ -59,9 +59,10 @@ class FarmEchoTask(WWOneTimeTask, BaseCombatTask):
dropped = self.yolo_find_echo()[0]
self.incr_drop(dropped)
self.sleep(0.5)
self.send_key('esc')
self.send_key('esc', after_sleep=0.5)
self.wait_click_feature('confirm_btn_hcenter_vcenter', relative_x=-1, raise_if_not_found=True,
use_gray_scale=True, settle_time=2)
post_action=lambda: self.send_key('esc', after_sleep=1),
settle_time=2)
self.wait_in_team_and_world(time_out=120)
self.sleep(2)
@ -74,12 +75,12 @@ class FarmEchoTask(WWOneTimeTask, BaseCombatTask):
self.click_relative(x, y + (start - 1) * distance)
self.sleep(0.5)
self.wait_click_feature('gray_button_challenge', raise_if_not_found=True, use_gray_scale=True,
self.wait_click_feature('gray_button_challenge', raise_if_not_found=True,
click_after_delay=0.5)
self.wait_click_feature('gray_confirm_exit_button', relative_x=-1, raise_if_not_found=False,
use_gray_scale=True, time_out=3, click_after_delay=0.5, threshold=0.8)
time_out=3, click_after_delay=0.5, threshold=0.8)
self.wait_click_feature('gray_start_battle', relative_x=-1, raise_if_not_found=True,
use_gray_scale=True, click_after_delay=0.5, threshold=0.8)
click_after_delay=0.5, threshold=0.8)
echo_color = {

View File

@ -1,58 +1,60 @@
import math
import re
import time
from typing import List
import cv2
import numpy as np
from qfluentwidgets import FluentIcon
from ok import Logger
from ok import Logger, Box, get_bounding_box
from src.task.BaseCombatTask import BaseCombatTask
from src.task.WWOneTimeTask import WWOneTimeTask
logger = Logger.get_logger(__name__)
class FarmMapTask(WWOneTimeTask, BaseCombatTask):
class BigMap(WWOneTimeTask, BaseCombatTask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.icon = FluentIcon.FLAG
self.description = "Farm selected Tacet Suppression until out of stamina, will use the backup stamina, you need to be able to teleport from the menu(F2)"
self.name = "Tacet Suppression (Must explore first to be able to teleport)"
default_config = {
'Which Tacet Suppression to Farm': 1 # starts with 1
}
self.row_per_page = 5
self.total_number = 11
default_config.update(self.default_config)
self.config_description = {
'Which Tacet Suppression to Farm': 'the Nth number in the Tacet Suppression list (F2)',
}
self.default_config = default_config
self.max_star_distance = 1000
self.last_star = None
self.last_angle = None
self.stuck_keys = [['space', 0.2], ['a',2], ['d',2]]
self.stuck_index = 0
self.last_cords = None
self.cords_re = re.compile('^\d+,\d+,\d+$')
self.big_map_frame = None
self.stars = None
self.bounding_box = None
@property
def star_move_distance_threshold(self):
return self.height_of_screen(0.02)
def run(self):
self.last_star = None
self.last_angle = None
self.go_to_star()
def reset(self):
self.frame = None
self.stars = None
def get_cords(self):
cords = self.ocr(0.01, 0.95, 0.21, 1, match=self.cords_re, log=True)
if cords:
return cords[0].name
def load_stars(self):
self.click_relative(0.94, 556 / 1080, after_sleep=1)
self.big_map_frame = self.frame
self.stars = self.find_feature('big_map_star', threshold=0.7, frame=self.big_map_frame, box=Box(0,0,self.big_map_frame.shape[1],self.big_map_frame.shape[0]))
all_star_len = len(self.stars)
self.stars = group_boxes_by_center_distance(self.stars, self.height_of_screen(0.2))
if len(self.stars) <= 2:
raise Exception('Need be in the map screen and have a path of at least 3 stars!')
self.log_info(f'Loaded {len(self.stars)} from {all_star_len} Stars', notify=True)
self.bounding_box = get_bounding_box(self.stars)
mini_map_box = self.get_box_by_name('box_minimap')
self.bounding_box.width += mini_map_box.width * 2
self.bounding_box.height += mini_map_box.height * 2
self.bounding_box.x -= mini_map_box.width
self.bounding_box.y -= mini_map_box.height
self.info_set('Stars', len(self.stars))
self.send_key('esc', after_sleep=1)
def get_angle(self):
def get_angle_between(self, my_angle, angle):
if my_angle > angle:
to_turn = angle - my_angle
else:
to_turn = -(my_angle - angle)
if to_turn > 180:
to_turn -= 360
elif to_turn < -180:
to_turn += 360
return to_turn
def get_my_angle(self):
arrow_template = self.get_feature_by_name('arrow')
original_mat = arrow_template.mat
max_conf = 0
@ -68,60 +70,141 @@ class FarmMapTask(WWOneTimeTask, BaseCombatTask):
for angle in range(0, 360):
# Rotate the template image
rotation_matrix = cv2.getRotationMatrix2D(center, -angle, 1.0)
arrow_template.mat = cv2.warpAffine(original_mat, rotation_matrix, (w, h))
arrow_template.mask = np.where(np.all(arrow_template.mat == [0, 0, 0], axis=2), 0, 255).astype(np.uint8)
template = cv2.warpAffine(original_mat, rotation_matrix, (w, h))
# mask = np.where(np.all(template == [0, 0, 0], axis=2), 0, 255).astype(np.uint8)
target = self.find_one(f'arrow_{angle}', box=target_box,
template=arrow_template, threshold=0.01)
target = self.find_one(box=target_box,
template=template, threshold=0.01)
# if self.debug and angle % 90 == 0:
# self.screenshot(f'arrow_rotated_{angle}', arrow_template.mat)
if target and target.confidence > max_conf:
max_conf = target.confidence
max_angle = angle
max_target = target
max_mat = arrow_template.mat
arrow_template.mat = original_mat
# max_target = target
# max_mat = template
# arrow_template.mat = original_mat
# arrow_template.mask = None
# if self.debug and max_mat is not None:
# self.screenshot('max_mat',frame=max_mat)
# self.log_debug(f'turn_east max_conf: {max_conf} {max_angle}')
return max_angle , max_target
return max_angle
def set_last_angle(self, angle):
self.last_angle = angle
self.info_set('Last Angle', angle)
def find_next_star(self):
stars = self.find_stars()
min_distance = self.max_star_distance
nearest_star = None
x2, y2 = self.get_box_by_name('box_minimap').center()
for star in stars:
if not self.last_star:
x1, y1 = star.center()
angle = calculate_angle_clockwise(x1, y1, x2, y2)
if self.last_angle is not None and abs(angle - self.last_angle) < 90:
self.log_debug(f'old path continue {abs(angle - self.last_angle)} {star}')
continue
self.last_star = star
self.log_debug(f'no last star return nearest {self.last_star} {self.last_angle}')
return -1, star, 360 - angle
distance = star.center_distance(self.last_star)
def find_direction_angle(self):
my_box = self.find_my_location()
min_distance = 100000
min_star = None
if len(self.stars) == 0:
return None, 0, 0
for star in self.stars:
distance = star.center_distance(my_box)
if distance < min_distance:
min_distance = distance
nearest_star = star
if nearest_star:
self.last_star = nearest_star
self.log_debug(f'nearest_star: {min_distance} {nearest_star}')
return min_distance, nearest_star, -1
min_star = star
self.draw_boxes('star', min_star, color='green')
# if self.debug:
# self.screenshot('parse_read_big_map', frame=self.big_map_frame, show_box=True)
direction_angle = calculate_angle_clockwise(my_box, min_star)
my_angle = self.get_my_angle()
to_turn = self.get_angle_between(my_angle, direction_angle)
self.log_debug(f'direction_angle {to_turn} {my_angle} {direction_angle} min_distance {min_distance} min_star {min_star} ')
return min_star, min_distance, to_turn
def remove_star(self, star):
before = len(self.stars)
self.stars.remove(star)
self.info_set('Stars', len(self.stars))
self.log_debug(f'removed star {before} -> {len(self.stars)}')
def find_my_location(self):
frame = self.big_map_frame
mat = self.get_box_by_name('box_minimap').crop_frame(self.frame)
mat = keep_circle(mat)
in_big_map = self.find_one(frame=frame, template=mat, threshold=0.01, box=self.bounding_box, mask_function=create_circle_mask_with_hole)
# in_big_maps = self.find_feature(frame=frame, template=mat, threshold=0.01, box=self.bounding_box)
if not in_big_map:
raise RuntimeError('can not find my cords on big map!')
self.log_debug(f'found big map: {in_big_map}')
if self.debug and in_big_map:
self.draw_boxes('stars', self.stars)
self.draw_boxes('search_map', self.bounding_box)
self.draw_boxes('in_big_map', in_big_map.scale(0.1), color='blue')
# self.screenshot('box_minimap', frame=mat, show_box=True)
return in_big_map
def keep_circle(img):
height, width = img.shape[:2]
# Create a black mask with the same dimensions
mask = np.zeros((height, width), dtype=np.uint8)
# Define circle parameters (center and radius)
center_x, center_y = width // 2, height // 2
radius = min(center_x, center_y) # Fit circle within image bounds
# Draw a filled white circle on the mask
cv2.circle(mask, (center_x, center_y), radius, (255), thickness=-1)
# Apply the mask to the original image using bitwise AND
result = cv2.bitwise_and(img, img, mask=mask)
return result
def create_circle_mask_with_hole(image):
"""
Creates a binary circular mask with a rectangular hole in the center.
The circle fills the mask dimensions, and the hole is 1/4 width and height.
Args:
shape (tuple): The (height, width) of the desired mask.
Returns:
numpy.ndarray: A uint8 NumPy array representing the mask
(255 in the circle ring, 0 elsewhere and in the hole).
"""
h, w = image.shape[:2]
mask = np.zeros((h, w), dtype=np.uint8)
center_x, center_y = w // 2, h // 2
radius = min(w, h) // 2
# 1. Draw the outer filled white circle
cv2.circle(mask, (center_x, center_y), radius, 255, -1)
# 2. Calculate rectangle dimensions and corners for the hole
rect_w = round(w / 4.4)
rect_h = round(h / 4.4)
# Calculate top-left corner centered
rect_x1 = center_x - rect_w // 2
rect_y1 = center_y - rect_h // 2
# Calculate bottom-right corner
rect_x2 = rect_x1 + rect_w
rect_y2 = rect_y1 + rect_h
# 3. Draw the inner filled black rectangle (the hole)
# Use color=0 and thickness=-1 to fill with black
cv2.rectangle(mask, (rect_x1, rect_y1), (rect_x2, rect_y2), 0, -1)
return mask
class FarmMapTask(BigMap):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.icon = FluentIcon.GLOBE
self.description = "Farm world map with a marked path of stars, start in the map screen"
self.name = "Farm Map with Star Path"
self.max_star_distance = 1000
self.stuck_keys = [['space', 0.2], ['a',2], ['d',2]]
self.stuck_index = 0
self.last_distance = 0
self.check_pick_echo = True
@property
def star_move_distance_threshold(self):
return self.height_of_screen(0.025)
def run(self):
self.stuck_index = 0
self.last_distance = 0
self.load_stars()
self.go_to_star()
def go_to_star(self):
return self.yolo_find_echo(use_color=False, walk=False)
current_direction = None
self.center_camera()
current_adjust = None
while True:
self.sleep(0.01)
self.middle_click(interval=1, after_sleep=0.2)
if self.in_combat():
if current_direction is not None:
self.mouse_up(key='right')
@ -129,45 +212,47 @@ class FarmMapTask(WWOneTimeTask, BaseCombatTask):
current_direction = None
self.combat_once()
while self.yolo_find_echo(use_color=False, walk=False)[1]:
self.incr_drop(True)
self.sleep(0.5)
continue
distance, star, last_angle = self.find_next_star()
star, distance, angle = self.find_direction_angle()
# self.draw_boxes('next_star', star, color='green')
if not star:
self.log_info('cannot find any stars, stop farming', notify=True)
break
if distance == 0:
if distance <= self.star_move_distance_threshold:
self.log_info(f'reached star {star} {distance} {self.star_move_distance_threshold}')
self.remove_star(star)
continue
elif distance >= self.height_of_screen(0.35):
self.log_error('too far from next star, stop farming', notify=True)
if self.debug:
self.screenshot('too_far',frame=self.big_map_frame,show_box=True)
self.screenshot('far',frame=self.get_box_by_name('box_minimap').crop(self.frame))
break
elif distance == self.last_distance:
logger.info(f'might be stuck, try {[self.stuck_index % 3]}')
self.send_key(self.stuck_keys[self.stuck_index % 3][0], down_time=self.stuck_keys[self.stuck_index % 3][1], after_sleep=0.5)
self.stuck_index += 1
continue
elif distance > self.star_move_distance_threshold:
if self.debug:
self.screenshot('star_moved')
self.log_info(f'star moved continue forward {distance} {self.star_move_distance_threshold}')
self.last_star = None
self.sleep(4)
continue
if last_angle > 0:
self.set_last_angle(last_angle)
angle = self.get_angle_to_star(star)
self.last_distance = distance
if current_direction == 'w':
if 4 <= angle <= 70:
if 15 <= angle <= 75:
minor_adjust = 'd'
elif -70 <= angle <= -4:
elif -75 <= angle <= -15:
minor_adjust = 'a'
else:
minor_adjust = None
if minor_adjust != current_adjust and minor_adjust is not None:
if current_adjust:
self.send_key_up(current_adjust)
# if self.debug:
# self.screenshot(f'minor_adjust_{minor_adjust}_{angle}')
self.log_info(f'minor_adjust to {minor_adjust}_{angle}')
if minor_adjust:
self.send_key_down(minor_adjust)
# self.center_camera()
self.sleep(0.5)
current_adjust = minor_adjust
self.sleep(0.1)
self.middle_click(down_time=0.1)
self.send_key_up(minor_adjust)
self.sleep(0.2)
continue
if current_adjust:
self.send_key_up(current_adjust)
@ -198,42 +283,22 @@ class FarmMapTask(WWOneTimeTask, BaseCombatTask):
self.mouse_up(key='right')
self.send_key_up(current_direction)
def get_angle_to_star(self, star):
x1, y1 = self.get_box_by_name('box_minimap').center()
x2, y2 = star.center()
target_angle = calculate_angle_clockwise(x1, y1, x2, y2)
my_angle = self.get_angle()[0]
if my_angle >= target_angle:
turn_angle = -(my_angle - target_angle)
else:
turn_angle = target_angle - my_angle
if turn_angle > 180:
turn_angle = 360 - turn_angle
if turn_angle < -180:
turn_angle = 360 + turn_angle
logger.debug(f'go to turn_angle {my_angle} {target_angle} {turn_angle}')
return turn_angle
def find_stars(self):
box_minimap = self.get_box_by_name('box_minimap')
stars = self.find_feature('star', threshold=0.65, box=box_minimap)
sorted_stars = sorted(stars, key=lambda star: - box_minimap.center_distance(star))
# if self.debug:
# self.screenshot('starts', show_box=True)
# self.screenshot('stars_mask', frame=mask_star(self.frame), show_box=True)
return sorted_stars
def calculate_angle_clockwise(x1, y1, x2, y2):
def calculate_angle_clockwise(box1, box2):
"""
Calculates angle (radians) from horizontal right to line (x1,y1)->(x2,y2).
Positive clockwise, negative counter-clockwise.
"""
x1, y1 = box1.center()
x2, y2 = box2.center()
dx = x2 - x1
dy = y2 - y1
# math.atan2(dy, dx) gives angle from positive x-axis, positive CCW.
# Negate for positive CW convention.
return math.degrees(math.atan2(dy, dx))
degree = math.degrees(math.atan2(dy, dx))
if degree < 0:
degree += 360
return degree
star_color = {
'r': (190, 220), # Red range
@ -241,6 +306,39 @@ star_color = {
'b': (190, 220) # Blue range
}
def group_boxes_by_center_distance(boxes: List[Box], distance_threshold: float) -> List[Box]:
"""
Groups boxes where any box is close (center_distance < threshold)
to any other box in the group (connected components).
Returns the largest group.
"""
if not boxes:
return []
n = len(boxes)
visited = [False] * n
all_groups = []
# Find connected components using DFS
for i in range(n):
if not visited[i]:
current_group = []
stack = [i]
visited[i] = True
while stack:
current_idx = stack.pop()
current_group.append(boxes[current_idx])
for j in range(n):
if not visited[j]:
# Use center_distance as requested
dist = boxes[current_idx].center_distance(boxes[j])
if dist < distance_threshold:
visited[j] = True
stack.append(j)
all_groups.append(current_group)
# Return the largest group found
if not all_groups:
return [] # Should not happen if boxes is not empty, but safe check
return max(all_groups, key=len)
def mask_star(image):
# return image
return create_color_mask(image, star_color)

View File

@ -43,25 +43,25 @@ class SkipBaseTask(BaseWWTask):
self.click_box(skip, move_back=True)
return self.wait_until(self.skip_confirm, time_out=3, raise_if_not_found=False)
if time.time() - self.has_eye_time < 2:
btn_dialog_close = self.find_one('btn_dialog_close', use_gray_scale=True, threshold=0.8)
btn_dialog_close = self.find_one('btn_dialog_close', threshold=0.8)
if btn_dialog_close:
self.click(btn_dialog_close, move_back=True)
return True
btn_dialog_eye = self.find_one('btn_dialog_eye', use_gray_scale=True, threshold=0.8)
btn_dialog_eye = self.find_one('btn_dialog_eye', threshold=0.8)
if btn_dialog_eye:
self.has_eye_time = time.time()
btn_auto_play_dialog = self.find_one('btn_auto_play_dialog', use_gray_scale=True)
btn_auto_play_dialog = self.find_one('btn_auto_play_dialog')
if btn_auto_play_dialog:
self.click_box(btn_auto_play_dialog, move_back=True)
logger.info('toggle auto play')
self.sleep(0.2)
if arrow := self.find_feature('btn_dialog_arrow', x=0.59, y=0.33, to_x=0.75, to_y=0.75,
use_gray_scale=True, threshold=0.7):
threshold=0.7):
self.click(arrow[-1])
logger.info('choose arrow')
self.sleep(0.2)
elif dots := self.find_feature('btn_dialog_3dots', x=0.59, y=0.33, to_x=0.75, to_y=0.75,
use_gray_scale=True, threshold=0.7):
threshold=0.7):
if dots:
self.click(dots[-1])
logger.info('choose dot')

View File

@ -16,18 +16,18 @@ class TestTacet(TaskTestCase):
self.logger.info(f'test_find_treasure_icon {angle, box}')
self.assertTrue(100 <= angle <= 200)
def test_find_stars(self):
self.set_image('tests/images/stars.png')
stars = self.task.find_stars()
self.logger.info(f'find_stars {stars}')
self.assertEqual(3, len(stars))
angle1 = self.task.get_angle_to_star(stars[0])
angle2 = self.task.get_angle_to_star(stars[1])
angle3 = self.task.get_angle_to_star(stars[2])
self.assertTrue(100 <= angle1 <= 180)
self.assertTrue(0 <= angle2 <= 90)
self.assertTrue(-45 <= angle3 <= 0)
# def test_find_stars(self):
# self.set_image('tests/images/stars.png')
# stars = self.task.find_stars()
# self.logger.info(f'find_stars {stars}')
# self.assertEqual(3, len(stars))
#
# angle1 = self.task.get_angle_to_star(stars[0])
# angle2 = self.task.get_angle_to_star(stars[1])
# angle3 = self.task.get_angle_to_star(stars[2])
# self.assertTrue(100 <= angle1 <= 180)
# self.assertTrue(0 <= angle2 <= 90)
# self.assertTrue(-45 <= angle3 <= 0)
if __name__ == '__main__':
unittest.main()

BIN
tests/images/big_map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB