Add: Attack abyssal boss

This commit is contained in:
LmeSzinc 2021-12-06 20:56:57 +08:00
parent 1290224c50
commit caf51858e9
11 changed files with 347 additions and 74 deletions

View File

@ -147,6 +147,7 @@ class ManualConfig:
HOMO_CORNER_THRESHOLD = 0.8
HOMO_RECTANGLE_THRESHOLD = 10
HOMO_EDGE_DETECT = True
HOMO_EDGE_HOUGHLINES_THRESHOLD = 120
HOMO_EDGE_COLOR_RANGE = (0, 24)
# ((x, y), [upper-left, upper-right, bottom-left, bottom-right])

View File

@ -126,14 +126,23 @@ class FastForwardHandler(AutoSearchHandler):
changed = fast_forward.set(status=status, main=self)
return changed
def handle_map_fleet_lock(self):
def handle_map_fleet_lock(self, enable=None):
"""
Args:
enable (bool): Default to None, use Campaign_UseFleetLock.
Returns:
bool: If switched.
"""
# Fleet lock depends on if it appear on map, not depends on map status.
# Because if already in map, there's no map status,
if not fleet_lock.appear(main=self):
logger.info('No fleet lock option.')
return False
status = 'on' if self.config.Campaign_UseFleetLock else 'off'
if enable is None:
enable = self.config.Campaign_UseFleetLock
status = 'on' if enable else 'off'
changed = fleet_lock.set(status=status, main=self)
return changed

View File

@ -12,7 +12,7 @@ def location_ensure(location):
Returns:
tuple(int): Location, such as (4, 3)
"""
if isinstance(location, GridInfo):
if hasattr(location, 'location'):
return location.location
elif isinstance(location, str):
return node2location(location)

View File

@ -43,9 +43,29 @@ class GridPredictor:
self.homo_invt = cv2.invert(self.homo_data)[1]
def screen2grid(self, points):
"""
Args:
points (np.ndarray): Coordinates from screen, [[x1, y1], [x2, y2], ...]
Returns:
np.ndarray: Coordinates from sea surface, [[x1, y1], [x2, y2], ...]
Coordinate zero point is the upper-left corner.
(0, 0) +------+
| |
| |
+------+ (1, 1)
"""
return perspective_transform(points, self.homo_data) / self.config.HOMO_TILE
def grid2screen(self, points):
"""
Args:
points (np.ndarray): Coordinates from sea surface, [[x1, y1], [x2, y2], ...]
See Also screen2grid().
Returns:
np.ndarray: Coordinates from screen, [[x1, y1], [x2, y2], ...]
"""
return perspective_transform(np.multiply(points, self.config.HOMO_TILE), self.homo_invt)
@cached_property

View File

@ -188,10 +188,13 @@ class Homography:
self.homo_loca %= self.config.HOMO_TILE
# Detect map edges
image_edge = cv2.bitwise_and(cv2.dilate(image_edge, kernel),
cv2.inRange(image_trans, *self.config.HOMO_EDGE_COLOR_RANGE))
image_edge = cv2.bitwise_and(image_edge, self.ui_mask_homo_stroke)
self.detect_edges(image_edge, hough_th=self.config.HOMO_EDGE_HOUGHLINES_THRESHOLD)
self.lower_edge, self.upper_edge, self.left_edge, self.right_edge = False, False, False, False
self._map_edge_count = (0, 0)
if self.config.HOMO_EDGE_DETECT:
image_edge = cv2.bitwise_and(cv2.dilate(image_edge, kernel),
cv2.inRange(image_trans, *self.config.HOMO_EDGE_COLOR_RANGE))
image_edge = cv2.bitwise_and(image_edge, self.ui_mask_homo_stroke)
self.detect_edges(image_edge, hough_th=self.config.HOMO_EDGE_HOUGHLINES_THRESHOLD)
# Log
time_cost = round(time.time() - start_time, 3)

View File

@ -2,7 +2,6 @@ import numpy as np
from module.base.button import Button
from module.base.decorator import cached_property
from module.base.timer import Timer
from module.exception import MapDetectionError
from module.logger import logger
from module.map.camera import Camera
@ -36,12 +35,12 @@ class OSCamera(OSMapOperation, Camera):
"""
return Radar(self.config)
def update_radar(self):
def predict_radar(self):
"""
Scan radar and merge it into map
"""
self.radar.predict(self.device.image)
self.map.update(self.radar, camera=self.fleet_current)
self.radar.show()
def grid_is_in_sight(self, grid, camera=None, sight=None):
location = location_ensure(grid)
@ -154,33 +153,6 @@ class OSCamera(OSMapOperation, Camera):
logger.info('Radar %s -> Local %s (fleet=%s)' % (
str(location),
location2node(local.location),
location2node(self.view.center_loca)
location2node(center)
))
return local
def wait_until_camera_stable(self, skip_first_screenshot=True):
"""
Wait until homo_loca stabled.
DETECTION_BACKEND must be 'homography'.
"""
logger.info('Wait until camera stable')
record = None
confirm_timer = Timer(0.3, count=0).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
self.update_os()
self.view.predict()
current = tuple(self.view.backend.homo_loca.tolist())
if record is None or (current is not None and current == record):
if confirm_timer.reached():
break
else:
confirm_timer.reset()
record = current
logger.info('Camera stabled')

View File

@ -4,6 +4,7 @@ class OSConfig:
"""
MAP_FOCUS_ENEMY_AFTER_BATTLE = True
MAP_HAS_SIREN = True
MAP_HAS_FLEET_STEP = True
IGNORE_LOW_EMOTION_WARN = False
MAP_GRID_CENTER_TOLERANCE = 0.2
@ -28,6 +29,7 @@ class OSConfig:
INTERNAL_LINES_HOUGHLINES_THRESHOLD = 75
EDGE_LINES_HOUGHLINES_THRESHOLD = 75
HOMO_EDGE_DETECT = False
HOMO_CANNY_THRESHOLD = (50, 50)
MAP_ENEMY_GENRE_DETECTION_SCALING = {

View File

@ -5,6 +5,8 @@ from module.exception import MapWalkError
from module.logger import logger
from module.map.fleet import Fleet
from module.map.map_grids import SelectedGrids
from module.map.utils import location_ensure
from module.map_detection.utils import *
from module.os.assets import TEMPLATE_EMPTY_HP
from module.os.camera import OSCamera
from module.os.map_base import OSCampaignMap
@ -12,10 +14,15 @@ from module.os_ash.ash import OSAsh
from module.os_combat.combat import Combat
def limit_walk(location, step=3):
x, y = location
return min(abs(x), step - abs(y)) * x // abs(x), y
class OSFleet(OSCamera, Combat, Fleet, OSAsh):
def _goto(self, location, expected=''):
super()._goto(location, expected)
self.update_radar()
self.predict_radar()
self.map.show()
if self.handle_ash_beacon_attack():
@ -165,6 +172,97 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh):
center = self.camera
return SelectedGrids(sea).sort_by_camera_distance(center)
def wait_until_camera_stable(self, skip_first_screenshot=True):
"""
Wait until homo_loca stabled.
DETECTION_BACKEND must be 'homography'.
"""
logger.hr('Wait until camera stable')
record = None
confirm_timer = Timer(0.3, count=0).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
self.update_os()
current = self.view.backend.homo_loca
logger.attr('homo_loca', current)
if record is None or (current is not None and np.linalg.norm(np.subtract(current, record)) < 3):
if confirm_timer.reached():
break
else:
confirm_timer.reset()
record = current
logger.info('Camera stabled')
def wait_until_walk_stable(self, skip_first_screenshot=False):
"""
Wait until homo_loca stabled.
DETECTION_BACKEND must be 'homography'.
Raises:
MapWalkError: If unable to goto such grid.
"""
logger.hr('Wait until walk stable')
record = None
enemy_searching_appear = False
confirm_timer = Timer(0.8, count=2).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# Map event
if self.handle_map_event():
confirm_timer.reset()
continue
if self.handle_walk_out_of_step():
raise MapWalkError('walk_out_of_step')
# Enemy searching
if not enemy_searching_appear and self.enemy_searching_appear():
enemy_searching_appear = True
confirm_timer.reset()
continue
else:
if enemy_searching_appear:
self.handle_enemy_flashing()
self.device.sleep(0.3)
logger.info('Enemy searching appeared.')
enemy_searching_appear = False
if self.is_in_map():
self.enemy_searching_color_initial()
# Combat
if self.combat_appear():
# Use ui_back() for testing, because there are too few abyssal loggers every month.
# self.ui_back(check_button=self.is_in_map)
self.combat(expected_end=self.is_in_map, fleet_index=self.fleet_show_index)
confirm_timer.reset()
continue
# Arrive
if self.is_in_map():
self.update_os()
current = self.view.backend.homo_loca
logger.attr('homo_loca', current)
if record is None or (current is not None and np.linalg.norm(np.subtract(current, record)) < 3):
if confirm_timer.reached():
break
else:
confirm_timer.reset()
record = current
else:
confirm_timer.reset()
logger.info('Walk stabled')
def port_goto(self):
"""
A simple and poor implement to goto port. Searching port on radar.
@ -179,43 +277,23 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh):
"""
while 1:
# Calculate destination
port = self.radar.port_predict(self.device.image)
logger.info(f'Port route at {port}')
if np.linalg.norm(port) == 0:
grid = self.radar.port_predict(self.device.image)
logger.info(f'Port route at {grid}')
if np.linalg.norm(grid) == 0:
logger.info('Arrive port')
break
# Update local view
self.update_os()
self.view.predict()
self.view.show()
self.predict()
# Click way point
port = point_limit(port, area=(-4, -2, 3, 2))
port = self.convert_radar_to_local(port)
self.device.click(port)
grid = point_limit(grid, area=(-4, -2, 3, 2))
grid = self.convert_radar_to_local(grid)
self.device.click(grid)
# Wait until arrived
prev = (0, 0)
confirm_timer = Timer(1, count=2).start()
backup = self.config.temporary(MAP_HAS_FLEET_STEP=True)
while 1:
self.device.screenshot()
if self.handle_walk_out_of_step():
backup.recover()
raise MapWalkError('walk_out_of_step')
self.radar.port_predict(self.device.image)
if np.linalg.norm(np.subtract(self.radar.port_loca, prev)) < 1:
if confirm_timer.reached():
break
else:
confirm_timer.reset()
prev = self.radar.port_loca
backup.recover()
self.wait_until_walk_stable()
def fleet_set(self, index=1, skip_first_screenshot=True):
"""
@ -226,9 +304,159 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh):
Returns:
bool: If switched.
"""
logger.info(f'Fleet set to {index}')
logger.hr(f'Fleet set to {index}')
if self.fleet_selector.ensure_to_be(index):
self.wait_until_camera_stable()
return True
else:
return False
def question_goto(self, has_fleet_step=False):
logger.hr('Question goto')
while 1:
# Update local view
# Not screenshots taking, reuse the old one
self.update_os()
self.predict()
self.predict_radar()
# Calculate destination
grids = self.radar.select(is_question=True)
if grids:
# Click way point
grid = location_ensure(grids[0])
grid = point_limit(grid, area=(-4, -2, 3, 2))
if has_fleet_step:
grid = limit_walk(grid)
grid = self.convert_radar_to_local(grid)
self.device.click(grid)
else:
logger.info('No question mark to goto, stop')
break
# Wait until arrived
# Having new screenshots
self.wait_until_walk_stable()
def boss_goto(self, location=(0, 0), has_fleet_step=False):
logger.hr('BOSS goto')
while 1:
# Update local view
# Not screenshots taking, reuse the old one
self.update_os()
self.predict()
self.predict_radar()
# Calculate destination
grids = self.radar.select(is_enemy=True)
if grids:
# Click way point
grid = np.add(location_ensure(grids[0]), location)
grid = point_limit(grid, area=(-4, -2, 3, 2))
if has_fleet_step:
grid = limit_walk(grid)
if grid == (0, 0):
logger.info(f'Arrive destination: boss {location}')
break
grid = self.convert_radar_to_local(grid)
self.device.click(grid)
else:
logger.info('No boss to goto, stop')
break
# Wait until arrived
# Having new screenshots
self.wait_until_walk_stable()
def boss_leave(self, skip_first_screenshot=True):
"""
Pages:
in: is_in_map(), or combat_appear()
out: is_in_map(), fleet not in boss.
"""
logger.hr('BOSS leave')
# Update local view
self.update_os()
click_timer = Timer(3)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# End
if self.is_in_map():
self.predict_radar()
if self.radar.select(is_enemy=True):
logger.info('Fleet left boss')
break
# Re-enter boss accidently
if self.combat_appear():
self.ui_back(check_button=self.is_in_map)
# Click leave button
if self.is_in_map() and click_timer.reached():
grid = self.view[self.view.center_loca]
# The left half grid next to the center grid.
area = corner2inner(grid.grid2screen(area2corner((1, 0.25, 1.5, 0.75))))
button = Button(area=area, color=(), button=area, name='BOSS_LEAVE')
self.device.click(button)
click_timer.reset()
def boss_clear(self, has_fleet_step=True):
"""
All fleets take turns in attacking the boss.
Args:
has_fleet_step (bool):
Returns:
bool: If success to clear.
Pages:
in: Siren logger (abyssal), boss appeared.
out: If success, dangerous or safe zone.
If failed, still in abyssal.
"""
logger.hr(f'BOSS clear', level=1)
fleets = [1, 2, 3, 4]
standby_grids = [(-1, -1), (0, -1), (1, -1), (0, 0)]
for fleet, standby in zip(fleets, standby_grids):
logger.hr(f'Try boss with fleet {fleet}', level=2)
self.fleet_set(fleet)
self.boss_goto(location=(0, 0), has_fleet_step=has_fleet_step)
# End
self.predict_radar()
if self.radar.select(is_question=True):
logger.info('BOSS clear')
self.map_exit()
return True
# Standby
self.boss_leave()
if standby == (0, 0):
break
self.boss_goto(location=standby, has_fleet_step=has_fleet_step)
logger.critical('Unable to clear boss, fleets exhausted')
return False
def run_abyssal(self):
"""
Handle double confirms and attack abyssal (siren logger) boss.
Returns:
bool: If success to clear.
Pages:
in: Siren logger (abyssal).
out: If success, in a dangerous or safe zone.
If failed, still in abyssal.
"""
self.handle_map_fleet_lock(enable=False)
self.question_goto(has_fleet_step=True)
result = self.boss_clear(has_fleet_step=True)
return result

View File

@ -278,10 +278,7 @@ class GlobeOperation(ActionPointHandler, MapEventHandler):
# End
if self.is_in_map():
if confirm_timer.reached():
break
else:
confirm_timer.reset()
break
if self.is_zone_pinned() and click_timer.reached():
self.device.click(ZONE_ENTRANCE)

View File

@ -4,6 +4,7 @@ from module.base.mask import Mask
from module.base.utils import *
from module.config.config import AzurLaneConfig
from module.logger import logger
from module.map.map_grids import SelectedGrids
from module.map_detection.utils import fit_points
MASK_RADAR = Mask('./assets/mask/MASK_OS_RADAR.png')
@ -31,7 +32,7 @@ class RadarGrid:
'ME': 'is_meowfficer',
'PO': 'is_port',
'QU': 'is_question',
# 'FL': 'is_fleet',
'FL': 'is_fleet',
}
def __init__(self, location, image, center, config):
@ -201,6 +202,25 @@ class Radar:
grid.reset()
grid.predict()
def select(self, **kwargs):
"""
Args:
**kwargs: Attributes of Grid.
Returns:
SelectedGrids:
"""
result = []
for grid in self:
flag = True
for k, v in kwargs.items():
if grid.__getattribute__(k) != v:
flag = False
if flag:
result.append(grid)
return SelectedGrids(result)
def predict_port_outside(self, image):
"""
Args:

View File

@ -77,6 +77,27 @@ class Combat(Combat_, MapEventHandler):
# self.emotion.reduce(fleet_index)
break
def handle_exp_info(self):
if self.is_combat_executing():
return False
if self.appear_then_click(EXP_INFO_S):
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(EXP_INFO_A):
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(EXP_INFO_B):
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(EXP_INFO_C):
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(EXP_INFO_D):
self.device.sleep((0.25, 0.5))
return True
return False
def handle_get_items(self, drop=None):
if self.appear(GET_ITEMS_1, offset=5, interval=self.battle_status_click_interval):
self.device.click(CLICK_SAFE_AREA)
@ -108,7 +129,7 @@ class Combat(Combat_, MapEventHandler):
return self.handle_os_in_map()
def combat_status(self, drop=None, expected_end=None):
super().combat_status(drop=drop, expected_end=self._os_combat_expected_end)
super().combat_status(drop=None, expected_end=self._os_combat_expected_end)
def combat(self, *args, **kwargs):
"""