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:
parent
7d3210532a
commit
51d9e4e441
BIN
assets/images/75.png
Normal file
BIN
assets/images/75.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/76.png
Normal file
BIN
assets/images/76.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 210 KiB |
File diff suppressed because it is too large
Load Diff
@ -160,6 +160,8 @@ config = {
|
||||
["src.task.TacetTask", "TacetTask"],
|
||||
["src.task.FarmEchoTask", "FarmEchoTask"],
|
||||
["src.task.FarmWorldBossTask", "FarmWorldBossTask"],
|
||||
# ["src.task.FarmMapTask", "FarmMapTask"],
|
||||
# ["src.task.Farm13CTask", "Farm13CTask"],
|
||||
["ok", "DiagnosisTask"],
|
||||
], 'trigger_tasks': [
|
||||
["src.task.AutoCombatTask", "AutoCombatTask"],
|
||||
|
@ -56,7 +56,7 @@ numpy==2.2.4
|
||||
# shapely
|
||||
rapidocr==2.0.6
|
||||
# via -r .\requirements.in
|
||||
ok-script==0.0.521
|
||||
ok-script==0.0.522
|
||||
# via -r .\requirements.in
|
||||
omegaconf==2.3.0
|
||||
# via ok-rapidocr-dml
|
||||
|
@ -1,7 +1,6 @@
|
||||
from qfluentwidgets import FluentIcon
|
||||
|
||||
from ok import Logger, BaseScene
|
||||
from src.task.BaseWWTask import BaseWWTask
|
||||
|
||||
logger = Logger.get_logger(__name__)
|
||||
|
||||
|
@ -20,6 +20,7 @@ class AutoCombatTask(BaseCombatTask, TriggerTask):
|
||||
self.scene: WWScene | None = None
|
||||
self.default_config.update({
|
||||
'Auto Target': True,
|
||||
'Auto Pick Echo After Combat': True,
|
||||
})
|
||||
self.config_description = {
|
||||
'Auto Target': 'Turn off to enable auto combat only when manually target enemy using middle click'
|
||||
@ -38,9 +39,10 @@ class AutoCombatTask(BaseCombatTask, TriggerTask):
|
||||
break
|
||||
except NotInCombatException as e:
|
||||
logger.info(f'auto_combat_task_out_of_combat {e}')
|
||||
# if self.debug:
|
||||
# self.screenshot(f'auto_combat_task_out_of_combat {e}')
|
||||
break
|
||||
if ret:
|
||||
self.combat_end()
|
||||
if self.config.get('Auto Pick Echo After Combat'):
|
||||
while self.yolo_find_echo(use_color=False, walk=False, turn=False)[1]:
|
||||
pass
|
||||
return ret
|
||||
|
@ -3,7 +3,7 @@ from qfluentwidgets import FluentIcon
|
||||
from ok import FindFeature, Logger
|
||||
from ok import TriggerTask
|
||||
from src.scene.WWScene import WWScene
|
||||
from src.task.BaseWWTask import BaseWWTask
|
||||
from src.task.BaseWWTask import BaseWWTask, f_white_color
|
||||
|
||||
logger = Logger.get_logger(__name__)
|
||||
|
||||
@ -69,8 +69,3 @@ class AutoPickTask(TriggerTask, BaseWWTask, FindFeature):
|
||||
return True
|
||||
self.next_frame()
|
||||
|
||||
f_white_color = {
|
||||
'r': (235, 255), # Red range
|
||||
'g': (235, 255), # Green range
|
||||
'b': (235, 255) # Blue range
|
||||
}
|
@ -8,10 +8,15 @@ from ok import BaseTask, Logger, find_boxes_by_name, og, Box
|
||||
from ok import CannotFindException
|
||||
import cv2
|
||||
|
||||
|
||||
logger = Logger.get_logger(__name__)
|
||||
number_re = re.compile(r'^(\d+)$')
|
||||
stamina_re = re.compile(r'^(\d+)/(\d+)$')
|
||||
|
||||
f_white_color = {
|
||||
'r': (235, 255), # Red range
|
||||
'g': (235, 255), # Green range
|
||||
'b': (235, 255) # Blue range
|
||||
}
|
||||
|
||||
class BaseWWTask(BaseTask):
|
||||
map_zoomed = False
|
||||
@ -112,7 +117,21 @@ class BaseWWTask(BaseTask):
|
||||
|
||||
def find_f_with_text(self, target_text=None):
|
||||
f = self.find_one('pick_up_f_hcenter_vcenter', box=self.f_search_box, threshold=0.8)
|
||||
if f and target_text:
|
||||
if not f:
|
||||
return None
|
||||
|
||||
start = time.time()
|
||||
percent = 0.0
|
||||
while time.time() - start < 1:
|
||||
percent = self.calculate_color_percentage(f_white_color, f)
|
||||
if percent > 0.5:
|
||||
break
|
||||
self.next_frame()
|
||||
self.log_debug(f'f white color percent: {percent} wait')
|
||||
if percent < 0.5:
|
||||
return None
|
||||
|
||||
if target_text:
|
||||
search_text_box = f.copy(x_offset=f.width * 5, width_offset=f.width * 7, height_offset=1.5 * f.height,
|
||||
y_offset=-0.8 * f.height, name='search_text_box')
|
||||
text = self.ocr(box=search_text_box, match=target_text, target_height=540)
|
||||
@ -129,11 +148,17 @@ class BaseWWTask(BaseTask):
|
||||
start = time.time()
|
||||
ended = False
|
||||
while time.time() - start < time_out:
|
||||
self.next_frame()
|
||||
if end_condition:
|
||||
ended = end_condition()
|
||||
if ended:
|
||||
break
|
||||
treasure_icon = find_function()
|
||||
if isinstance(treasure_icon, list):
|
||||
if len(treasure_icon) > 0:
|
||||
treasure_icon = treasure_icon[0]
|
||||
else:
|
||||
treasure_icon = None
|
||||
if not treasure_icon:
|
||||
if not end_condition:
|
||||
self.log_info('find_function not found, break')
|
||||
@ -142,6 +167,7 @@ class BaseWWTask(BaseTask):
|
||||
self.next_frame()
|
||||
continue
|
||||
x, y = treasure_icon.center()
|
||||
self.draw_boxes(treasure_icon)
|
||||
y = max(0, y - self.height_of_screen(0.05))
|
||||
next_direction = self.get_direction(x, y, self.width, self.height)
|
||||
if next_direction != last_direction:
|
||||
@ -149,8 +175,8 @@ class BaseWWTask(BaseTask):
|
||||
self.send_key_up(last_direction)
|
||||
self.sleep(0.02)
|
||||
last_direction = next_direction
|
||||
self.send_key_down(next_direction)
|
||||
self.next_frame()
|
||||
if next_direction:
|
||||
self.send_key_down(next_direction)
|
||||
if last_direction:
|
||||
self.send_key_up(last_direction)
|
||||
self.sleep(0.02)
|
||||
@ -375,7 +401,7 @@ class BaseWWTask(BaseTask):
|
||||
logger.info(f"found a claim reward")
|
||||
return True
|
||||
|
||||
def find_echo(self, threshold=0.5) -> Box:
|
||||
def find_echo(self, threshold=0.5):
|
||||
"""
|
||||
Main function to load ONNX model, perform inference, draw bounding boxes, and display the output image.
|
||||
|
||||
@ -387,10 +413,25 @@ class BaseWWTask(BaseTask):
|
||||
list: List of dictionaries containing detection information such as class_id, class_name, confidence, etc.
|
||||
"""
|
||||
# Load the ONNX model
|
||||
boxes = og.my_app.yolo_detect(self.frame, label=12)
|
||||
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)
|
||||
return ret
|
||||
|
||||
def yolo_find_all(self, threshold=0.3):
|
||||
"""
|
||||
Main function to load ONNX model, perform inference, draw bounding boxes, and display the output image.
|
||||
|
||||
Args:
|
||||
onnx_model (str): Path to the ONNX model.
|
||||
input_image (ndarray): Path to the input image.
|
||||
|
||||
Returns:
|
||||
list: List of dictionaries containing detection information such as class_id, class_name, confidence, etc.
|
||||
"""
|
||||
# Load the ONNX model
|
||||
boxes = og.my_app.yolo_detect(self.frame, threshold=threshold, label=-1)
|
||||
ret = sorted(boxes, key=lambda detection: detection.confidence, reverse=True)
|
||||
if ret:
|
||||
return ret[0]
|
||||
return ret
|
||||
|
||||
def pick_echo(self):
|
||||
if self.find_f_with_text(target_text=self.absorb_echo_text()):
|
||||
@ -398,22 +439,26 @@ class BaseWWTask(BaseTask):
|
||||
if not self.handle_claim_button():
|
||||
return True
|
||||
|
||||
def yolo_find_echo(self, use_color=True):
|
||||
def yolo_find_echo(self, use_color=True, walk=True, turn=True):
|
||||
max_echo_count = 0
|
||||
if self.pick_echo():
|
||||
self.sleep(0.5)
|
||||
return True
|
||||
return True, True
|
||||
front_box = self.box_of_screen(0.35, 0.35, 0.65, 0.53, hcenter=True)
|
||||
color_threshold = 0.02
|
||||
for i in range(4):
|
||||
self.center_camera()
|
||||
echo = self.find_echo()
|
||||
if turn:
|
||||
self.center_camera()
|
||||
echos = self.find_echo()
|
||||
if self.debug:
|
||||
self.draw_boxes('echo', echo)
|
||||
if echo and echo.center()[1] > self.height_of_screen(0.55):
|
||||
self.log_info(f'yolo found echo {echo}')
|
||||
self.draw_boxes('echo', echos)
|
||||
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):
|
||||
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)
|
||||
return self.walk_to_box(self.find_echo, time_out=15, end_condition=self.pick_echo), 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}')
|
||||
@ -421,17 +466,26 @@ class BaseWWTask(BaseTask):
|
||||
if self.debug:
|
||||
self.screenshot('echo_color_picked')
|
||||
self.log_debug(f'found color_percent {color_percent} > {color_threshold}, walk now')
|
||||
return self.walk_find_echo()
|
||||
return self.walk_find_echo(), max_echo_count > 1
|
||||
if not turn and i==0:
|
||||
return False, max_echo_count > 1
|
||||
self.send_key('a', down_time=0.05)
|
||||
self.sleep(0.5)
|
||||
|
||||
self.center_camera()
|
||||
picked = self.walk_find_echo()
|
||||
return picked
|
||||
if walk:
|
||||
picked = self.walk_find_echo()
|
||||
return picked, max_echo_count > 1
|
||||
return False, max_echo_count > 1
|
||||
|
||||
def center_camera(self):
|
||||
self.click(0.5, 0.5, down_time=0.2, after_sleep=0.5, key='middle')
|
||||
|
||||
def turn_direction(self, direction):
|
||||
if direction != 'w':
|
||||
self.send_key(direction, down_time=0.05, after_sleep=0.5)
|
||||
self.center_camera()
|
||||
|
||||
def walk_find_echo(self, backward_time=1):
|
||||
if self.walk_until_f(time_out=4, backward_time=backward_time, target_text=self.absorb_echo_text(),
|
||||
raise_if_not_found=False): # find and pick echo
|
||||
@ -441,6 +495,7 @@ class BaseWWTask(BaseTask):
|
||||
def incr_drop(self, dropped):
|
||||
if dropped:
|
||||
self.info['Echo Count'] = self.info.get('Echo Count', 0) + 1
|
||||
self.info['Echo per Hour'] = round(self.info.get('Echo Count', 0) / max(time.time() - self.start_time, 1) * 3600)
|
||||
|
||||
def should_check_monthly_card(self):
|
||||
if self.next_monthly_card_start > 0:
|
||||
|
234
src/task/Farm13CTask.py
Normal file
234
src/task/Farm13CTask.py
Normal file
@ -0,0 +1,234 @@
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from qfluentwidgets import FluentIcon
|
||||
|
||||
from ok import Logger
|
||||
from src.task.BaseCombatTask import BaseCombatTask
|
||||
from src.task.WWOneTimeTask import WWOneTimeTask
|
||||
|
||||
logger = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class Farm13CTask(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+$')
|
||||
|
||||
@property
|
||||
def star_move_distance_threshold(self):
|
||||
return self.height_of_screen(0.005)
|
||||
|
||||
def run(self):
|
||||
self.last_star = None
|
||||
self.last_angle = None
|
||||
while True:
|
||||
echos = self.yolo_find_all()
|
||||
self.draw_boxes("yolo", echos)
|
||||
self.sleep(0.1)
|
||||
|
||||
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 get_angle(self):
|
||||
arrow_template = self.get_feature_by_name('arrow')
|
||||
original_mat = arrow_template.mat
|
||||
max_conf = 0
|
||||
max_angle = 0
|
||||
max_target = None
|
||||
max_mat = None
|
||||
(h, w) = arrow_template.mat.shape[:2]
|
||||
# self.log_debug(f'turn_east h:{h} w:{w}')
|
||||
center = (w // 2, h // 2)
|
||||
target_box = self.get_box_by_name('box_arrow')
|
||||
# if self.debug:
|
||||
# self.screenshot('arrow_original', original_ mat)
|
||||
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)
|
||||
|
||||
target = self.find_one(f'arrow_{angle}', box=target_box,
|
||||
template=arrow_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
|
||||
# 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
|
||||
|
||||
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
|
||||
distance = star.center_distance(self.last_star)
|
||||
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
|
||||
|
||||
def go_to_star(self):
|
||||
current_direction = None
|
||||
self.center_camera()
|
||||
while True:
|
||||
self.sleep(0.01)
|
||||
if self.in_combat():
|
||||
if current_direction is not None:
|
||||
self.mouse_up(key='right')
|
||||
self.send_key_up(current_direction)
|
||||
current_direction = None
|
||||
self.combat_once()
|
||||
while self.yolo_find_echo(use_color=False, walk=False)[1]:
|
||||
self.sleep(0.5)
|
||||
cords = self.get_cords()
|
||||
if cords and cords == self.last_cords:
|
||||
logger.info(f'might be stuck, try jump')
|
||||
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
|
||||
self.last_cords = cords
|
||||
stars = self.find_stars()
|
||||
if not stars:
|
||||
self.log_info('cannot find any stars, stop farming', notify=True)
|
||||
break
|
||||
star = stars[0]
|
||||
angle = self.get_angle_to_star(star)
|
||||
if current_direction == 'w':
|
||||
if 4 <= angle <= 90:
|
||||
minor_adjust = 'd'
|
||||
elif -90 <= angle <= -4:
|
||||
minor_adjust = 'a'
|
||||
else:
|
||||
minor_adjust = None
|
||||
if minor_adjust:
|
||||
# if self.debug:
|
||||
# self.screenshot(f'minor_adjust_{minor_adjust}_{angle}')
|
||||
self.log_info(f'minor_adjust to {minor_adjust}_{angle}')
|
||||
self.send_key_down(minor_adjust)
|
||||
# self.center_camera()
|
||||
self.sleep(0.3)
|
||||
self.send_key_up(minor_adjust)
|
||||
continue
|
||||
if -45 <= angle <= 45:
|
||||
new_direction = 'w'
|
||||
elif 45 < angle <= 135:
|
||||
new_direction = 'd'
|
||||
elif -135 < angle <= -45:
|
||||
new_direction = 'a'
|
||||
else:
|
||||
new_direction = 's'
|
||||
if current_direction != new_direction:
|
||||
self.log_info(f'changed direction {angle} {current_direction} -> {new_direction}')
|
||||
if self.debug:
|
||||
self.screenshot(f'{current_direction}_{new_direction}_{angle}')
|
||||
if current_direction:
|
||||
self.send_key_up(current_direction)
|
||||
self.sleep(0.2)
|
||||
self.turn_direction(new_direction)
|
||||
self.send_key_down('w')
|
||||
self.sleep(0.2)
|
||||
self.mouse_down(key='right')
|
||||
current_direction = 'w'
|
||||
self.sleep(1)
|
||||
|
||||
if current_direction is not None:
|
||||
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.5, 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):
|
||||
"""
|
||||
Calculates angle (radians) from horizontal right to line (x1,y1)->(x2,y2).
|
||||
Positive clockwise, negative counter-clockwise.
|
||||
"""
|
||||
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))
|
||||
|
||||
star_color = {
|
||||
'r': (190, 220), # Red range
|
||||
'g': (190, 220), # Green range
|
||||
'b': (190, 220) # Blue range
|
||||
}
|
||||
|
||||
def mask_star(image):
|
||||
# return image
|
||||
return create_color_mask(image, star_color)
|
||||
|
||||
def create_color_mask(image, color_ranges):
|
||||
mask = cv2.inRange(image, (color_ranges['b'][0], color_ranges['g'][0], color_ranges['r'][0]), (color_ranges['b'][1], color_ranges['g'][1], color_ranges['r'][1]))
|
||||
return mask
|
@ -1,3 +1,5 @@
|
||||
import time
|
||||
|
||||
from qfluentwidgets import FluentIcon
|
||||
|
||||
from ok import Logger
|
||||
@ -27,7 +29,7 @@ class FarmEchoTask(WWOneTimeTask, BaseCombatTask):
|
||||
self.add_exit_after_config()
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
WWOneTimeTask.run(self)
|
||||
self.set_check_monthly_card()
|
||||
self.ensure_main(time_out=180)
|
||||
if not self.in_team()[0]:
|
||||
@ -54,7 +56,7 @@ class FarmEchoTask(WWOneTimeTask, BaseCombatTask):
|
||||
|
||||
self.combat_once()
|
||||
logger.info(f'farm echo move {self.config.get("Boss")} walk_until_f to find echo')
|
||||
dropped = self.yolo_find_echo()
|
||||
dropped = self.yolo_find_echo()[0]
|
||||
self.incr_drop(dropped)
|
||||
self.sleep(0.5)
|
||||
self.send_key('esc')
|
||||
@ -63,10 +65,6 @@ class FarmEchoTask(WWOneTimeTask, BaseCombatTask):
|
||||
self.wait_in_team_and_world(time_out=120)
|
||||
self.sleep(2)
|
||||
|
||||
def incr_drop(self, dropped):
|
||||
if dropped:
|
||||
self.info['Echo Count'] = self.info.get('Echo Count', 0) + 1
|
||||
|
||||
def choose_level(self, start):
|
||||
y = 0.17
|
||||
x = 0.15
|
||||
|
250
src/task/FarmMapTask.py
Normal file
250
src/task/FarmMapTask.py
Normal file
@ -0,0 +1,250 @@
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from qfluentwidgets import FluentIcon
|
||||
|
||||
from ok import Logger
|
||||
from src.task.BaseCombatTask import BaseCombatTask
|
||||
from src.task.WWOneTimeTask import WWOneTimeTask
|
||||
|
||||
logger = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class FarmMapTask(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+$')
|
||||
|
||||
@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 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 get_angle(self):
|
||||
arrow_template = self.get_feature_by_name('arrow')
|
||||
original_mat = arrow_template.mat
|
||||
max_conf = 0
|
||||
max_angle = 0
|
||||
max_target = None
|
||||
max_mat = None
|
||||
(h, w) = arrow_template.mat.shape[:2]
|
||||
# self.log_debug(f'turn_east h:{h} w:{w}')
|
||||
center = (w // 2, h // 2)
|
||||
target_box = self.get_box_by_name('box_arrow')
|
||||
# if self.debug:
|
||||
# self.screenshot('arrow_original', original_ mat)
|
||||
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)
|
||||
|
||||
target = self.find_one(f'arrow_{angle}', box=target_box,
|
||||
template=arrow_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
|
||||
# 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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
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)
|
||||
if self.in_combat():
|
||||
if current_direction is not None:
|
||||
self.mouse_up(key='right')
|
||||
self.send_key_up(current_direction)
|
||||
current_direction = None
|
||||
self.combat_once()
|
||||
while self.yolo_find_echo(use_color=False, walk=False)[1]:
|
||||
self.sleep(0.5)
|
||||
continue
|
||||
distance, star, last_angle = self.find_next_star()
|
||||
if not star:
|
||||
self.log_info('cannot find any stars, stop farming', notify=True)
|
||||
break
|
||||
if distance == 0:
|
||||
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)
|
||||
if current_direction == 'w':
|
||||
if 4 <= angle <= 70:
|
||||
minor_adjust = 'd'
|
||||
elif -70 <= angle <= -4:
|
||||
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}')
|
||||
self.send_key_down(minor_adjust)
|
||||
# self.center_camera()
|
||||
self.sleep(0.5)
|
||||
current_adjust = minor_adjust
|
||||
continue
|
||||
if current_adjust:
|
||||
self.send_key_up(current_adjust)
|
||||
current_adjust = None
|
||||
if -45 <= angle <= 45:
|
||||
new_direction = 'w'
|
||||
elif 45 < angle <= 135:
|
||||
new_direction = 'd'
|
||||
elif -135 < angle <= -45:
|
||||
new_direction = 'a'
|
||||
else:
|
||||
new_direction = 's'
|
||||
if current_direction != new_direction:
|
||||
self.log_info(f'changed direction {angle} {current_direction} -> {new_direction}')
|
||||
if self.debug:
|
||||
self.screenshot(f'{current_direction}_{new_direction}_{angle}')
|
||||
if current_direction:
|
||||
self.send_key_up(current_direction)
|
||||
self.sleep(0.2)
|
||||
self.turn_direction(new_direction)
|
||||
self.send_key_down('w')
|
||||
self.sleep(0.2)
|
||||
self.mouse_down(key='right')
|
||||
current_direction = 'w'
|
||||
self.sleep(1)
|
||||
|
||||
if current_direction is not None:
|
||||
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):
|
||||
"""
|
||||
Calculates angle (radians) from horizontal right to line (x1,y1)->(x2,y2).
|
||||
Positive clockwise, negative counter-clockwise.
|
||||
"""
|
||||
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))
|
||||
|
||||
star_color = {
|
||||
'r': (190, 220), # Red range
|
||||
'g': (190, 220), # Green range
|
||||
'b': (190, 220) # Blue range
|
||||
}
|
||||
|
||||
def mask_star(image):
|
||||
# return image
|
||||
return create_color_mask(image, star_color)
|
||||
|
||||
def create_color_mask(image, color_ranges):
|
||||
mask = cv2.inRange(image, (color_ranges['b'][0], color_ranges['g'][0], color_ranges['r'][0]), (color_ranges['b'][1], color_ranges['g'][1], color_ranges['r'][1]))
|
||||
return mask
|
@ -49,7 +49,7 @@ class FarmWorldBossTask(WWOneTimeTask, BaseCombatTask):
|
||||
# not current in use because not stable, right now using one click to scroll down
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
WWOneTimeTask.run(self)
|
||||
self.ensure_main(time_out=180)
|
||||
self.set_check_monthly_card()
|
||||
count = 0
|
||||
@ -90,7 +90,7 @@ class FarmWorldBossTask(WWOneTimeTask, BaseCombatTask):
|
||||
self.sleep(5)
|
||||
logger.info(f'farm echo move forward walk_until_f to find echo')
|
||||
|
||||
dropped = self.yolo_find_echo()
|
||||
dropped = self.yolo_find_echo()[0]
|
||||
self.incr_drop(dropped)
|
||||
|
||||
if count < 2:
|
||||
|
@ -5,4 +5,6 @@ class WWOneTimeTask:
|
||||
|
||||
def run(self):
|
||||
mouse_reset_task = self.executor.get_task_by_class(MouseResetTask)
|
||||
mouse_reset_task.run()
|
||||
mouse_reset_task.run()
|
||||
self.executor.interaction.activate()
|
||||
self.sleep(0.5)
|
33
tests/TestMap.py
Normal file
33
tests/TestMap.py
Normal file
@ -0,0 +1,33 @@
|
||||
import unittest
|
||||
from config import config
|
||||
from ok.test.TaskTestCase import TaskTestCase
|
||||
from src.task.FarmMapTask import FarmMapTask
|
||||
|
||||
config['debug'] = True
|
||||
|
||||
|
||||
class TestTacet(TaskTestCase):
|
||||
task_class = FarmMapTask
|
||||
config = config
|
||||
|
||||
def test_find_treasure_icon(self):
|
||||
self.set_image('tests/images/angle_130.png')
|
||||
angle, box = self.task.get_angle()
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
BIN
tests/images/angle_130.png
Normal file
BIN
tests/images/angle_130.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.9 MiB |
BIN
tests/images/stars.png
Normal file
BIN
tests/images/stars.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 MiB |
Loading…
x
Reference in New Issue
Block a user