diff --git a/assets/cn/handler/AIR_ATTACK_CONFIRM.png b/assets/cn/handler/AIR_ATTACK_CONFIRM.png new file mode 100644 index 000000000..0f5af1f13 Binary files /dev/null and b/assets/cn/handler/AIR_ATTACK_CONFIRM.png differ diff --git a/assets/cn/handler/AIR_ATTACK_ENTER.png b/assets/cn/handler/AIR_ATTACK_ENTER.png new file mode 100644 index 000000000..ef8b8b975 Binary files /dev/null and b/assets/cn/handler/AIR_ATTACK_ENTER.png differ diff --git a/assets/cn/map/FLEET_SUPPORT_EMPTY.png b/assets/cn/map/FLEET_SUPPORT_EMPTY.png new file mode 100644 index 000000000..cdb45ca5c Binary files /dev/null and b/assets/cn/map/FLEET_SUPPORT_EMPTY.png differ diff --git a/campaign/campaign_main/campaign_16_1.py b/campaign/campaign_main/campaign_16_1.py new file mode 100644 index 000000000..96092589b --- /dev/null +++ b/campaign/campaign_main/campaign_16_1.py @@ -0,0 +1,86 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-1') +MAP.shape = 'I9' +MAP.camera_data = ['C2', 'C6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['F6'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + ++ ME -- -- ME -- -- ME -- + ME -- -- ME ++ ME -- -- ME + -- -- ME -- ME -- -- ME ++ + -- ME ++ ME -- ME -- ME Me + -- -- ME -- Me ++ __ -- -- + -- ME -- __ ME -- -- -- MB + -- ++ Me -- -- -- Me ++ ++ + -- ME -- -- -- -- -- ++ ++ + -- -- -- -- SP SP ++ ++ -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1}, + {'battle': 5, 'boss': 1} +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + + # ===== End of generated config ===== + MAP_HAS_SUBMARINE_SUPPORT = True + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.clear_boss() + + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_6(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_main/campaign_16_2.py b/campaign/campaign_main/campaign_16_2.py new file mode 100644 index 000000000..6138b10d4 --- /dev/null +++ b/campaign/campaign_main/campaign_16_2.py @@ -0,0 +1,82 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-2') +MAP.shape = 'J8' +MAP.camera_data = ['C2', 'C6', 'G2', 'G6'] +MAP.camera_data_spawn_point = ['C6'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + MB ++ -- ME -- -- -- -- -- -- + -- Me ME -- ME ++ ME -- -- -- + -- __ -- -- -- -- -- ME ++ ++ + ME -- ME ME ME -- ME -- ++ -- + Me -- -- Me ++ -- Me -- -- -- + ++ ME -- ME ME -- ME ++ ++ -- + -- -- -- -- -- Me -- -- ++ -- + -- SP SP -- ++ ++ ++ -- -- -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1}, + {'battle': 5, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + # ===== End of generated config ===== + MAP_HAS_SUBMARINE_SUPPORT = True + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.clear_boss() + + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_6(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_main/campaign_16_3.py b/campaign/campaign_main/campaign_16_3.py new file mode 100644 index 000000000..244b7e55d --- /dev/null +++ b/campaign/campaign_main/campaign_16_3.py @@ -0,0 +1,99 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase, CampaignMap +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-3') +MAP.shape = 'K6' +MAP.camera_data = ['C2', 'C5', 'F2', 'F5', 'H2', 'H5'] +MAP.camera_data_spawn_point = ['C5'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + -- -- ++ ++ ++ -- -- ME ++ -- MB + -- ME -- ++ -- ME -- -- ++ -- -- + -- -- ME ME -- ME ++ ME ++ -- -- + -- -- -- ++ ++ __ ME ME -- -- -- + SP -- -- ++ -- ME ++ -- -- -- -- + SP -- -- ME ME -- ++ -- -- -- ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 40 40 40 40 50 50 50 50 50 + 50 50 50 50 50 40 40 40 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3}, + {'battle': 1, 'enemy': 6}, + {'battle': 2, 'enemy': 3}, + {'battle': 3, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ + = MAP.flatten() + +roads = [RoadGrids([C3, D3, F3, G4, H4])] + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + # ===== End of generated config ===== + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (120, 255 - 17), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 50, 255), + 'prominence': 10, + 'distance': 50, + 'wlen': 1000 + } + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 25 + EDGE_LINES_HOUGHLINES_THRESHOLD = 25 + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if not self.map_is_clear_mode: + self.destroy_land_base(D2, C3, D3) + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_3(self): + if not self.map_is_clear_mode: + self.destroy_land_base(K6, J5, J6) + + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.brute_clear_boss() + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() diff --git a/campaign/campaign_main/campaign_16_4.py b/campaign/campaign_main/campaign_16_4.py new file mode 100644 index 000000000..5205ee989 --- /dev/null +++ b/campaign/campaign_main/campaign_16_4.py @@ -0,0 +1,107 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase, CampaignMap +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-4') +MAP.shape = 'K8' +MAP.camera_data = ['C2', 'C6', 'F2', 'F6', 'H2', 'H6'] +MAP.camera_data_spawn_point = ['C6'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + -- -- ++ -- -- -- ++ ME -- -- MB + ME ++ ++ ++ -- -- ME ++ -- -- -- + -- -- ME -- -- ++ ++ ME -- -- -- + -- -- -- ME ++ -- ME -- ++ ++ -- + -- -- ME -- -- ME ++ -- ME ++ -- + -- __ -- ++ ++ -- ++ ME ME -- -- + SP -- -- ME -- -- ME ++ -- ++ ++ + SP -- -- -- ++ -- ++ ++ -- -- ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 40 50 50 50 + 50 50 50 40 50 40 40 40 50 50 50 + 50 50 50 40 40 40 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 5}, + {'battle': 1, 'enemy': 4}, + {'battle': 2, 'enemy': 5}, + {'battle': 3}, + {'battle': 4, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ + = MAP.flatten() + +roads = [RoadGrids([D4, F5, G4, H3])] + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + # ===== End of generated config ===== + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (120, 255 - 17), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 50, 255), + 'prominence': 10, + 'distance': 50, + 'wlen': 1000 + } + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 25 + EDGE_LINES_HOUGHLINES_THRESHOLD = 25 + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if not self.map_is_clear_mode: + self.destroy_land_base(C1, D1, D1) + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_4(self): + if not self.map_is_clear_mode: + # destroy K8 and G8 land base in one air attack + self.destroy_land_base(K8, J6, I8) + + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.brute_clear_boss() + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() diff --git a/campaign/campaign_main/campaign_16_base.py b/campaign/campaign_main/campaign_16_base.py new file mode 100644 index 000000000..e58285218 --- /dev/null +++ b/campaign/campaign_main/campaign_16_base.py @@ -0,0 +1,211 @@ +import numpy as np + +from campaign.campaign_main.campaign_15_base import MASK_MAP_UI_W15 +from module.base.timer import Timer +from module.campaign.campaign_base import CampaignBase as CampaignBase_ +from module.logger import logger +from module.map.assets import FLEET_SUPPORT_EMPTY +from module.map.map_base import CampaignMap as CampaignMap_ +from module.map.map_grids import SelectedGrids +from module.map.utils import location_ensure +from module.map_detection.grid import GridInfo +from module.map_detection.utils_assets import ASSETS + + +class CampaignMap(CampaignMap_): + def update(self, grids, camera, mode='normal'): + """ + Args: + grids: + camera (tuple): + mode (str): Scan mode, such as 'init', 'normal', 'carrier', 'movable' + """ + offset = np.array(camera) - np.array(grids.center_loca) + + for grid in grids.grids.values(): + loca = tuple(offset + grid.location) + if loca in self.grids: + if self.ignore_prediction_match(globe=loca, local=grid): + continue + self.grids[loca].merge(grid, mode=mode) + if mode == 'init': + self.fixup_submarine_fleet() + return True + + +class Config: + # Ambushes can be avoid by having more DDs. + MAP_WALK_TURNING_OPTIMIZE = False + MAP_HAS_MYSTERY = False + + # HOMO_CANNY_THRESHOLD = (50, 100) + # MAP_SWIPE_MULTIPLY = (0.993, 1.011) + # MAP_SWIPE_MULTIPLY_MINITOUCH = (0.960, 0.978) + # MAP_SWIPE_MULTIPLY_MAATOUCH = (0.932, 0.949) + MAP_SWIPE_MULTIPLY = (1.391, 1.417) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.345, 1.370) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.306, 1.329) + + +class CampaignBase(CampaignBase_): + ENEMY_FILTER = '1L > 1M > 1E > 2L > 3L > 2M > 2E > 1C > 2C > 3M > 3E > 3C' + has_support_fleet = True + destroyed_land_base = [] + + def map_init(self, map_): + if self.config.MAP_HAS_SUBMARINE_SUPPORT and self.has_support_fleet: + logger.hr(f'{self.FUNCTION_NAME_BASE}SUBMARINE', level=2) + self.combat(balance_hp=False, emotion_reduce=False, save_get_items=False) + super().map_init(map_) + + def map_data_init(self, map_): + super().map_data_init(map_) + self.destroyed_land_base = [] + # Patch ui_mask, get rid of supporting fleet + _ = ASSETS.ui_mask + ASSETS.ui_mask = MASK_MAP_UI_W15.image + + def can_use_auto_search_continue(self): + return False + + def fleet_preparation(self, skip_first_screenshot=True): + if self.appear(FLEET_SUPPORT_EMPTY, offset=(5, 5)): + self.has_support_fleet = False + logger.attr('Has support fleet', self.has_support_fleet) + return super().fleet_preparation(skip_first_screenshot=skip_first_screenshot) + + def strategy_set_execute(self, formation=None, sub_view=None, sub_hunt=None): + super().strategy_set_execute( + formation=formation, + sub_view=sub_view, + sub_hunt=sub_hunt, + ) + logger.attr("Map has air attack", self.strategy_has_air_attack()) + + def _map_swipe(self, vector, box=(239, 159, 1175, 628)): + # Left border to 239, avoid swiping on support fleet + return super()._map_swipe(vector, box=box) + + def air_attackable(self, location): + """ + Check if air attack can be used at location. + This requires that: + 1. location grid is in the map (not exceeding the boundaries) + 2. location is not a land grid + + Args: + location (tuple): Location of air attack. + + Returns: + bool: if attackable. + """ + location = location_ensure(location) + attackable = True + + try: + logger.info(f'location: {self.map[location]}') + except KeyError as e: + logger.exception(f'Given coordinates are outside the map.') + raise e + + if self.map[location].is_land: + logger.error(f'{self.map[location]} is a land grid.') + attackable = False + + if not attackable: + logger.error(f'Cannot air attack at {self.map[location]}.') + + return attackable + + def _air_attack(self, location): + """ + Select the location for air attack. + + Args: + location (tuple, str, GridInfo): Location of air attack. + + Returns: + bool: If selected. + + Pages: + in: AIR_ATTACK_CONFIRM + out: AIR_ATTACK_CONFIRM + """ + location = location_ensure(location) + + self.in_sight(location) + grid = self.convert_global_to_local(location) + grid.__str__ = location + + logger.info('Select mob to move') + skip_first_screenshot = True + interval = Timer(2, count=4) + clicked_count = 0 + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if self.is_in_strategy_mob_move(): + self.view.update(image=self.device.image) + # temporary method to end + if clicked_count >= 1: + break + # Click + if interval.reached() and self.is_in_strategy_air_attack(): + self.device.click(grid) + clicked_count += 1 + interval.reset() + continue + + def air_attack(self, location): + """ + Open strategy, use air attack at location, close strategy. + + Args: + location (tuple, str, GridInfo): Location of air attack. + + Returns: + bool: If attacked + + Pages: + in: IN_MAP + out: IN_MAP + """ + if not self.air_attackable(location): + return False + + self.strategy_open() + if not self.strategy_has_air_attack(): + logger.warning(f'No remain air attack trials, will abandon attacking') + self.strategy_close() + return False + self.strategy_air_attack_enter() + self._air_attack(location) + self.strategy_air_attack_confirm() + self.strategy_close(skip_first_screenshot=False) + return True + + def destroy_land_base(self, land_base_grid, goto_grid, attack_grid): + """ + Args: + land_base_grid (GridInfo): location of land base + goto_grid (GridInfo): location for current fleet to go to + attack_grid (GridInfo): location to use air attack + + Returns: + bool: False + """ + if land_base_grid in self.destroyed_land_base: + logger.info(f'Land base {land_base_grid} already destroyed') + elif goto_grid.is_accessible: + logger.info(f'Destroy land base on {land_base_grid}') + self.goto(goto_grid, turning_optimize=self.config.MAP_WALK_TURNING_OPTIMIZE) + if self.air_attack(attack_grid): + self.destroyed_land_base.append(land_base_grid) + else: + logger.info(f'Land base {land_base_grid} not accessible, will check in next battle') + + return False diff --git a/module/config/config_manual.py b/module/config/config_manual.py index 764b75006..5b08399df 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -146,6 +146,7 @@ class ManualConfig: MAP_HAS_MISSILE_ATTACK = False # event_202111229_cn, missile attack covers the feature area of sirens. MAP_HAS_BOUNCING_ENEMY = False # event_20220224_cn, enemy is bouncing in a fixed route. MAP_HAS_DECOY_ENEMY = False # event_20220428, decoy enemy on map, disappear when fleet reach there. + MAP_HAS_SUBMARINE_SUPPORT = False # campaign 16-1 and 16-2 has submarine support fleet. MAP_FOCUS_ENEMY_AFTER_BATTLE = False # Operation siren MAP_ENEMY_TEMPLATE = ['Light', 'Main', 'Carrier', 'Treasure'] MAP_SIREN_TEMPLATE = ['DD', 'CL', 'CA', 'BB', 'CV'] diff --git a/module/handler/assets.py b/module/handler/assets.py index 63b8de7d1..764fdbb1c 100644 --- a/module/handler/assets.py +++ b/module/handler/assets.py @@ -4,6 +4,8 @@ from module.base.template import Template # This file was automatically generated by dev_tools/button_extract.py. # Don't modify it manually. +AIR_ATTACK_CONFIRM = Button(area={'cn': (1161, 645, 1222, 675), 'en': (1161, 645, 1222, 675), 'jp': (1161, 645, 1222, 675), 'tw': (1161, 645, 1222, 675)}, color={'cn': (130, 166, 212), 'en': (130, 166, 212), 'jp': (130, 166, 212), 'tw': (130, 166, 212)}, button={'cn': (1161, 645, 1222, 675), 'en': (1161, 645, 1222, 675), 'jp': (1161, 645, 1222, 675), 'tw': (1161, 645, 1222, 675)}, file={'cn': './assets/cn/handler/AIR_ATTACK_CONFIRM.png', 'en': './assets/cn/handler/AIR_ATTACK_CONFIRM.png', 'jp': './assets/cn/handler/AIR_ATTACK_CONFIRM.png', 'tw': './assets/cn/handler/AIR_ATTACK_CONFIRM.png'}) +AIR_ATTACK_ENTER = Button(area={'cn': (1194, 456, 1249, 530), 'en': (1194, 456, 1249, 530), 'jp': (1194, 456, 1249, 530), 'tw': (1194, 456, 1249, 530)}, color={'cn': (123, 124, 131), 'en': (123, 124, 131), 'jp': (123, 124, 131), 'tw': (123, 124, 131)}, button={'cn': (1194, 456, 1249, 530), 'en': (1194, 456, 1249, 530), 'jp': (1194, 456, 1249, 530), 'tw': (1194, 456, 1249, 530)}, file={'cn': './assets/cn/handler/AIR_ATTACK_ENTER.png', 'en': './assets/cn/handler/AIR_ATTACK_ENTER.png', 'jp': './assets/cn/handler/AIR_ATTACK_ENTER.png', 'tw': './assets/cn/handler/AIR_ATTACK_ENTER.png'}) ANDROID_NO_RESPOND = Button(area={'cn': (341, 433, 391, 472), 'en': (341, 433, 391, 472), 'jp': (341, 433, 391, 472), 'tw': (341, 433, 391, 472)}, color={'cn': (217, 237, 235), 'en': (217, 237, 235), 'jp': (217, 237, 235), 'tw': (217, 237, 235)}, button={'cn': (341, 433, 391, 472), 'en': (341, 433, 391, 472), 'jp': (341, 433, 391, 472), 'tw': (341, 433, 391, 472)}, file={'cn': './assets/cn/handler/ANDROID_NO_RESPOND.png', 'en': './assets/en/handler/ANDROID_NO_RESPOND.png', 'jp': './assets/jp/handler/ANDROID_NO_RESPOND.png', 'tw': './assets/tw/handler/ANDROID_NO_RESPOND.png'}) AUTO_SEARCH_MAP_OPTION_OFF = Button(area={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1204, 547, 1276, 568), 'tw': (1205, 546, 1275, 567)}, color={'cn': (196, 169, 169), 'en': (151, 132, 138), 'jp': (179, 153, 156), 'tw': (153, 132, 137)}, button={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1204, 547, 1276, 568), 'tw': (1205, 546, 1275, 567)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'en': './assets/en/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MAP_OPTION_OFF.png'}) AUTO_SEARCH_MAP_OPTION_ON = Button(area={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1203, 547, 1276, 568), 'tw': (1204, 546, 1276, 567)}, color={'cn': (149, 176, 193), 'en': (113, 135, 157), 'jp': (132, 158, 177), 'tw': (110, 133, 156)}, button={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1203, 547, 1276, 568), 'tw': (1204, 546, 1276, 567)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'en': './assets/en/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MAP_OPTION_ON.png'}) diff --git a/module/handler/fast_forward.py b/module/handler/fast_forward.py index 77723c217..0d055562d 100644 --- a/module/handler/fast_forward.py +++ b/module/handler/fast_forward.py @@ -572,3 +572,9 @@ class FastForwardHandler(AutoSearchHandler): self.device.click(MAP_WALK_SPEEDUP) interval.reset() continue + + def handle_submarine_cost_popup(self): + if self.config.MAP_HAS_SUBMARINE_SUPPORT and self.handle_popup_confirm('SUBMARINE_COST'): + return True + + return False diff --git a/module/handler/strategy.py b/module/handler/strategy.py index 4cf9a4956..4e1649757 100644 --- a/module/handler/strategy.py +++ b/module/handler/strategy.py @@ -259,3 +259,59 @@ class StrategyHandler(InfoHandler): if self.appear_then_click(MOB_MOVE_CANCEL, offset=(20, 20), interval=5): continue + + def is_in_strategy_air_attack(self): + """ + Returns: + bool: + """ + return self.appear(AIR_ATTACK_CONFIRM, offset=(20, 20)) + + def strategy_has_air_attack(self): + """ + Pages: + in: STRATEGY_OPENED + out: STRATEGY_OPENED + """ + if self.match_template_color(AIR_ATTACK_ENTER, offset=MOB_MOVE_OFFSET): + return True + else: + return False + + def strategy_air_attack_enter(self, skip_first_screenshot=True): + """ + Pages: + in: STRATEGY_OPENED, AIR_ATTACK_ENTER + out: AIR_ATTACK_CONFIRM + """ + logger.info('Air attack enter') + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(AIR_ATTACK_CONFIRM, offset=(20, 20)): + break + + if self.appear_then_click(AIR_ATTACK_ENTER, offset=MOB_MOVE_OFFSET, interval=5): + continue + + def strategy_air_attack_confirm(self, skip_first_screenshot=True): + """ + Pages: + in: AIR_ATTACK_CONFIRM + out: STRATEGY_OPENED, AIR_ATTACK_ENTER + """ + logger.info('Air attack confirm') + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(AIR_ATTACK_ENTER, offset=MOB_MOVE_OFFSET): + break + + if self.appear_then_click(AIR_ATTACK_CONFIRM, offset=(20, 20), interval=5): + continue diff --git a/module/map/assets.py b/module/map/assets.py index 2b2713fdf..5263daa87 100644 --- a/module/map/assets.py +++ b/module/map/assets.py @@ -21,6 +21,7 @@ FLEET_NUM_1 = Button(area={'cn': (213, 76, 224, 101), 'en': (213, 76, 224, 101), FLEET_NUM_2 = Button(area={'cn': (212, 75, 226, 101), 'en': (212, 75, 226, 101), 'jp': (212, 75, 226, 101), 'tw': (212, 75, 226, 101)}, color={'cn': (52, 150, 194), 'en': (52, 150, 194), 'jp': (52, 150, 194), 'tw': (52, 150, 194)}, button={'cn': (212, 75, 226, 101), 'en': (212, 75, 226, 101), 'jp': (212, 75, 226, 101), 'tw': (212, 75, 226, 101)}, file={'cn': './assets/cn/map/FLEET_NUM_2.png', 'en': './assets/en/map/FLEET_NUM_2.png', 'jp': './assets/jp/map/FLEET_NUM_2.png', 'tw': './assets/tw/map/FLEET_NUM_2.png'}) FLEET_PREPARATION = Button(area={'cn': (1013, 558, 1141, 588), 'en': (1048, 569, 1086, 595), 'jp': (1046, 558, 1107, 587), 'tw': (1014, 557, 1142, 588)}, color={'cn': (242, 211, 160), 'en': (241, 201, 148), 'jp': (241, 205, 151), 'tw': (242, 208, 157)}, button={'cn': (980, 549, 1181, 612), 'en': (988, 556, 1145, 606), 'jp': (983, 549, 1185, 612), 'tw': (980, 548, 1180, 612)}, file={'cn': './assets/cn/map/FLEET_PREPARATION.png', 'en': './assets/en/map/FLEET_PREPARATION.png', 'jp': './assets/jp/map/FLEET_PREPARATION.png', 'tw': './assets/tw/map/FLEET_PREPARATION.png'}) FLEET_PREPARATION_CHECK = Button(area={'cn': (1146, 107, 1174, 136), 'en': (1129, 111, 1158, 140), 'jp': (1146, 107, 1174, 136), 'tw': (1145, 106, 1175, 136)}, color={'cn': (180, 98, 111), 'en': (189, 105, 109), 'jp': (180, 98, 111), 'tw': (180, 90, 92)}, button={'cn': (1146, 107, 1174, 136), 'en': (1129, 111, 1158, 140), 'jp': (1146, 107, 1174, 136), 'tw': (1145, 106, 1175, 136)}, file={'cn': './assets/cn/map/FLEET_PREPARATION_CHECK.png', 'en': './assets/en/map/FLEET_PREPARATION_CHECK.png', 'jp': './assets/jp/map/FLEET_PREPARATION_CHECK.png', 'tw': './assets/tw/map/FLEET_PREPARATION_CHECK.png'}) +FLEET_SUPPORT_EMPTY = Button(area={'cn': (454, 470, 538, 540), 'en': (454, 470, 538, 540), 'jp': (454, 470, 538, 540), 'tw': (454, 470, 538, 540)}, color={'cn': (47, 54, 77), 'en': (47, 54, 77), 'jp': (47, 54, 77), 'tw': (47, 54, 77)}, button={'cn': (454, 470, 538, 540), 'en': (454, 470, 538, 540), 'jp': (454, 470, 538, 540), 'tw': (454, 470, 538, 540)}, file={'cn': './assets/cn/map/FLEET_SUPPORT_EMPTY.png', 'en': './assets/cn/map/FLEET_SUPPORT_EMPTY.png', 'jp': './assets/cn/map/FLEET_SUPPORT_EMPTY.png', 'tw': './assets/cn/map/FLEET_SUPPORT_EMPTY.png'}) MAP_CAT_ATTACK = Button(area={'cn': (1237, 103, 1252, 153), 'en': (1237, 103, 1252, 153), 'jp': (1237, 103, 1252, 153), 'tw': (1237, 103, 1252, 153)}, color={'cn': (43, 45, 52), 'en': (43, 45, 52), 'jp': (43, 45, 52), 'tw': (43, 45, 52)}, button={'cn': (1148, 653, 1262, 705), 'en': (1147, 651, 1263, 701), 'jp': (1149, 653, 1261, 704), 'tw': (1148, 653, 1262, 705)}, file={'cn': './assets/cn/map/MAP_CAT_ATTACK.png', 'en': './assets/en/map/MAP_CAT_ATTACK.png', 'jp': './assets/jp/map/MAP_CAT_ATTACK.png', 'tw': './assets/tw/map/MAP_CAT_ATTACK.png'}) MAP_CAT_ATTACK_MIRROR = Button(area={'cn': (147, 145, 187, 157), 'en': (147, 145, 187, 157), 'jp': (147, 145, 187, 157), 'tw': (147, 145, 187, 157)}, color={'cn': (214, 191, 99), 'en': (214, 191, 99), 'jp': (214, 191, 99), 'tw': (214, 191, 99)}, button={'cn': (147, 145, 187, 157), 'en': (147, 145, 187, 157), 'jp': (147, 145, 187, 157), 'tw': (147, 145, 187, 157)}, file={'cn': './assets/cn/map/MAP_CAT_ATTACK_MIRROR.png', 'en': './assets/en/map/MAP_CAT_ATTACK_MIRROR.png', 'jp': './assets/jp/map/MAP_CAT_ATTACK_MIRROR.png', 'tw': './assets/tw/map/MAP_CAT_ATTACK_MIRROR.png'}) MAP_MODE_SWITCH_HARD = Button(area={'cn': (341, 580, 374, 617), 'en': (341, 580, 374, 617), 'jp': (341, 580, 374, 617), 'tw': (341, 580, 374, 617)}, color={'cn': (234, 179, 179), 'en': (234, 179, 179), 'jp': (234, 179, 179), 'tw': (234, 179, 179)}, button={'cn': (341, 580, 374, 617), 'en': (341, 580, 374, 617), 'jp': (341, 580, 374, 617), 'tw': (341, 580, 374, 617)}, file={'cn': './assets/cn/map/MAP_MODE_SWITCH_HARD.png', 'en': './assets/cn/map/MAP_MODE_SWITCH_HARD.png', 'jp': './assets/cn/map/MAP_MODE_SWITCH_HARD.png', 'tw': './assets/cn/map/MAP_MODE_SWITCH_HARD.png'}) diff --git a/module/map/camera.py b/module/map/camera.py index 280a71712..b5a8c7c52 100644 --- a/module/map/camera.py +++ b/module/map/camera.py @@ -114,7 +114,8 @@ class Camera(MapOperation): try: if not self.is_in_map() \ and not self.is_in_strategy_submarine_move() \ - and not self.is_in_strategy_mob_move(): + and not self.is_in_strategy_mob_move() \ + and not self.is_in_strategy_air_attack(): logger.warning('Image to detect is not in_map') raise MapDetectionError('Image to detect is not in_map') self.view.load(self.device.image) diff --git a/module/map/map_operation.py b/module/map/map_operation.py index cffde2948..c06e8e22f 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -211,6 +211,9 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand if self.handle_2x_book_popup(): continue + if self.handle_submarine_cost_popup(): + continue + # Story skip if self.handle_story_skip(): campaign_timer.reset()