diff --git a/assets/cn/campaign/CHAPTER_20241024_EX.png b/assets/cn/campaign/CHAPTER_20241219_EX.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_EX.png rename to assets/cn/campaign/CHAPTER_20241219_EX.png diff --git a/assets/cn/campaign/CHAPTER_20241024_AB.png b/assets/cn/campaign/CHAPTER_20241219_PART1.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_AB.png rename to assets/cn/campaign/CHAPTER_20241219_PART1.png diff --git a/assets/cn/campaign/CHAPTER_20241024_CD.png b/assets/cn/campaign/CHAPTER_20241219_PART2.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_CD.png rename to assets/cn/campaign/CHAPTER_20241219_PART2.png diff --git a/assets/cn/campaign/CHAPTER_20241024_SP.png b/assets/cn/campaign/CHAPTER_20241219_SP.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_SP.png rename to assets/cn/campaign/CHAPTER_20241219_SP.png diff --git a/assets/cn/campaign/SWITCH_20240725_COMBAT.png b/assets/cn/campaign/SWITCH_20241219_COMBAT.png similarity index 100% rename from assets/cn/campaign/SWITCH_20240725_COMBAT.png rename to assets/cn/campaign/SWITCH_20241219_COMBAT.png diff --git a/assets/cn/campaign/SWITCH_20240725_STORY.png b/assets/cn/campaign/SWITCH_20241219_STORY.png similarity index 100% rename from assets/cn/campaign/SWITCH_20240725_STORY.png rename to assets/cn/campaign/SWITCH_20241219_STORY.png diff --git a/assets/tw/campaign/SWITCH_20240725_STORY.png b/assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png similarity index 76% rename from assets/tw/campaign/SWITCH_20240725_STORY.png rename to assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png index d460fe466..477c9a064 100644 Binary files a/assets/tw/campaign/SWITCH_20240725_STORY.png and b/assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png differ diff --git a/assets/en/campaign/SWITCH_20240725_COMBAT.png b/assets/en/campaign/SWITCH_20240725_COMBAT.png deleted file mode 100644 index dea5a57fc..000000000 Binary files a/assets/en/campaign/SWITCH_20240725_COMBAT.png and /dev/null differ diff --git a/assets/en/campaign/SWITCH_20240725_STORY.png b/assets/en/campaign/SWITCH_20240725_STORY.png deleted file mode 100644 index d460fe466..000000000 Binary files a/assets/en/campaign/SWITCH_20240725_STORY.png and /dev/null differ diff --git a/assets/jp/campaign/SWITCH_20240725_COMBAT.png b/assets/jp/campaign/SWITCH_20240725_COMBAT.png deleted file mode 100644 index dea5a57fc..000000000 Binary files a/assets/jp/campaign/SWITCH_20240725_COMBAT.png and /dev/null differ diff --git a/assets/jp/campaign/SWITCH_20240725_STORY.png b/assets/jp/campaign/SWITCH_20240725_STORY.png deleted file mode 100644 index d460fe466..000000000 Binary files a/assets/jp/campaign/SWITCH_20240725_STORY.png and /dev/null differ diff --git a/assets/jp/commission/COMMISSION_START.BUTTON.png b/assets/jp/commission/COMMISSION_START.BUTTON.png deleted file mode 100644 index 40f01bb22..000000000 Binary files a/assets/jp/commission/COMMISSION_START.BUTTON.png and /dev/null differ diff --git a/assets/jp/commission/COMMISSION_START.png b/assets/jp/commission/COMMISSION_START.png index 310b37020..87d853884 100644 Binary files a/assets/jp/commission/COMMISSION_START.png and b/assets/jp/commission/COMMISSION_START.png differ diff --git a/assets/tw/campaign/SWITCH_20240725_COMBAT.png b/assets/tw/campaign/SWITCH_20240725_COMBAT.png deleted file mode 100644 index dea5a57fc..000000000 Binary files a/assets/tw/campaign/SWITCH_20240725_COMBAT.png and /dev/null differ diff --git a/campaign/Readme.md b/campaign/Readme.md index a8868c653..8a49eb0b6 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -222,3 +222,6 @@ To add a new event, add a new row in here, and run `python -m module.config.conf | 20241114 | event 20220915 cn | Violet Tempest Blooming Lycoris Rerun | 复刻紫绛槿岚 | Violet Tempest Blooming Lycoris Rerun | 赫の涙月 菫の暁風(復刻) | - | | 20241114 | event 20240229 cn | Snowrealm Peregrination | - | - | - | 雪境迷蹤 | | 20241121 | event 20241121 cn | Dangerous Inventions Incoming | 危险发明迫近中 | Dangerous Inventions Incoming | 危険発明接近中 | - | +| 20241128 | event 20241121 cn | Dangerous Inventions | - | - | - | 危險發明逼近中 | +| 20241219 | event 20241219 cn | Substellar Crepuscule | 星光下的余晖 | Substellar Crepuscule | 星降る夕影の残光 | - | +| 20241219 | event 20231221 cn | Light-Chasing Sea of Stars | - | - | - | 星海逐光 | diff --git a/campaign/campaign_main/campaign_15_base.py b/campaign/campaign_main/campaign_15_base.py index 6e723b7bb..af7a5b841 100644 --- a/campaign/campaign_main/campaign_15_base.py +++ b/campaign/campaign_main/campaign_15_base.py @@ -53,9 +53,9 @@ class CampaignBase(CampaignBase_): map_has_mob_move = True - def strategy_set_execute(self, formation_index=None, sub_view=None, sub_hunt=None): + def strategy_set_execute(self, formation=None, sub_view=None, sub_hunt=None): super().strategy_set_execute( - formation_index=formation_index, + formation=formation, sub_view=sub_view, sub_hunt=sub_hunt, ) diff --git a/campaign/campaign_war_archives/campaign_base.py b/campaign/campaign_war_archives/campaign_base.py index ecfac7b81..76e0bac68 100644 --- a/campaign/campaign_war_archives/campaign_base.py +++ b/campaign/campaign_war_archives/campaign_base.py @@ -12,8 +12,8 @@ from module.war_archives.assets import (WAR_ARCHIVES_CAMPAIGN_CHECK, from module.war_archives.dictionary import dic_archives_template WAR_ARCHIVES_SWITCH = Switch('War_Archives_switch', is_selector=True) -WAR_ARCHIVES_SWITCH.add_status('ex', WAR_ARCHIVES_EX_ON) -WAR_ARCHIVES_SWITCH.add_status('sp', WAR_ARCHIVES_SP_ON) +WAR_ARCHIVES_SWITCH.add_state('ex', WAR_ARCHIVES_EX_ON) +WAR_ARCHIVES_SWITCH.add_state('sp', WAR_ARCHIVES_SP_ON) WAR_ARCHIVES_SCROLL = Scroll(WAR_ARCHIVES_SCROLL, color=(247, 211, 66), name='WAR_ARCHIVES_SCROLL') diff --git a/campaign/event_20221124_cn/campaign_base.py b/campaign/event_20221124_cn/campaign_base.py index 676c7c97b..29fd7794c 100644 --- a/campaign/event_20221124_cn/campaign_base.py +++ b/campaign/event_20221124_cn/campaign_base.py @@ -1,6 +1,6 @@ from module.campaign.campaign_base import CampaignBase as CampaignBase_ from module.combat.assets import GET_ITEMS_1_RYZA -from module.handler.fast_forward import auto_search +from module.handler.fast_forward import AUTO_SEARCH from module.handler.assets import MYSTERY_ITEM from module.logger import logger from module.map.map_grids import SelectedGrids @@ -62,7 +62,7 @@ class CampaignBase(CampaignBase_): # Chapter TH has no map_percentage and no 3_stars if name.startswith('th') or name.startswith('ht'): - appear = auto_search.appear(main=self) + appear = AUTO_SEARCH.appear(main=self) self.map_is_100_percent_clear = self.map_is_3_stars = self.map_is_threat_safe = appear self.map_has_clear_mode = appear self.map_show_info() diff --git a/campaign/event_20240725_cn/campaign_base.py b/campaign/event_20240725_cn/campaign_base.py index f4ca6680f..c45576306 100644 --- a/campaign/event_20240725_cn/campaign_base.py +++ b/campaign/event_20240725_cn/campaign_base.py @@ -1,11 +1,4 @@ -from module.campaign.assets import SWITCH_20240725_COMBAT, SWITCH_20240725_STORY from module.campaign.campaign_base import CampaignBase as CampaignBase_ -from module.campaign.campaign_ui import ModeSwitch -from module.logger import logger - -MODE_SWITCH_20240725 = ModeSwitch('Mode_switch_20240725', offset=(30, 30)) -MODE_SWITCH_20240725.add_status('combat', SWITCH_20240725_COMBAT) -MODE_SWITCH_20240725.add_status('story', SWITCH_20240725_STORY) class CampaignBase(CampaignBase_): @@ -20,9 +13,4 @@ class CampaignBase(CampaignBase_): if mode == 'hard': self.config.override(Campaign_Mode='hard') - if mode in ['normal', 'hard', 'ex']: - MODE_SWITCH_20240725.set('combat', main=self) - elif mode in ['story']: - MODE_SWITCH_20240725.set('story', main=self) - else: - logger.warning(f'Unknown campaign mode: {mode}') + self.campaign_ensure_mode_20241219(mode) diff --git a/campaign/event_20240829_cn/campaign_base.py b/campaign/event_20240829_cn/campaign_base.py index 608acabc7..fb4cc4e30 100644 --- a/campaign/event_20240829_cn/campaign_base.py +++ b/campaign/event_20240829_cn/campaign_base.py @@ -1,11 +1,5 @@ -from module.campaign.assets import SWITCH_20240725_COMBAT, SWITCH_20240725_STORY -from module.campaign.campaign_base import CampaignBase as CampaignBase_ -from module.campaign.campaign_ui import ModeSwitch -from module.logger import logger -MODE_SWITCH_20240725 = ModeSwitch('Mode_switch_20240725', offset=(30, 30)) -MODE_SWITCH_20240725.add_status('combat', SWITCH_20240725_COMBAT) -MODE_SWITCH_20240725.add_status('story', SWITCH_20240725_STORY) +from module.campaign.campaign_base import CampaignBase as CampaignBase_ class CampaignBase(CampaignBase_): @@ -20,12 +14,7 @@ class CampaignBase(CampaignBase_): if mode == 'hard': self.config.override(Campaign_Mode='hard') - if mode in ['normal', 'hard', 'ex']: - MODE_SWITCH_20240725.set('combat', main=self) - elif mode in ['story']: - MODE_SWITCH_20240725.set('story', main=self) - else: - logger.warning(f'Unknown campaign mode: {mode}') + self.campaign_ensure_mode_20241219(mode) @staticmethod def _campaign_separate_name(name): diff --git a/campaign/event_20240912_cn/campaign_base.py b/campaign/event_20240912_cn/campaign_base.py index 9a81e471c..e2baab617 100644 --- a/campaign/event_20240912_cn/campaign_base.py +++ b/campaign/event_20240912_cn/campaign_base.py @@ -1,12 +1,11 @@ -from module.campaign.assets import SWITCH_20240725_COMBAT, SWITCH_20240725_STORY +from module.campaign.assets import SWITCH_20241219_COMBAT, SWITCH_20241219_STORY from module.campaign.campaign_base import CampaignBase as CampaignBase_ from module.campaign.campaign_ui import ModeSwitch from module.ui.ui import page_event - -MODE_SWITCH_20240912 = ModeSwitch('Mode_switch_20240912', is_selector=True, offset=(30, 30)) -MODE_SWITCH_20240912.add_status('combat', SWITCH_20240725_COMBAT, offset=(444, 4)) -MODE_SWITCH_20240912.add_status('story', SWITCH_20240725_STORY, offset=(444, 4)) +MODE_SWITCH_20240912 = ModeSwitch('Mode_switch_20240912', is_selector=True) +MODE_SWITCH_20240912.add_state('combat', SWITCH_20241219_COMBAT, offset=(444, 4)) +MODE_SWITCH_20240912.add_state('story', SWITCH_20241219_STORY, offset=(444, 4)) class CampaignBase(CampaignBase_): @@ -18,6 +17,9 @@ class CampaignBase(CampaignBase_): Returns: bool: If mode changed. """ + # event_20240912_cn has two mode switches at bottom + # The classic one, MODE_SWITCH_* is at bottom-left, + # and MODE_SWITCH_20240912 is at bottom-middle if mode == "story": MODE_SWITCH_20240912.set('story', main=self) elif mode in ['normal', 'hard', 'ex']: diff --git a/campaign/event_20241024_cn/campaign_base.py b/campaign/event_20241024_cn/campaign_base.py index 849294076..b67c773ad 100644 --- a/campaign/event_20241024_cn/campaign_base.py +++ b/campaign/event_20241024_cn/campaign_base.py @@ -1,21 +1,10 @@ from module.base.utils import color_similarity_2d -from module.campaign.assets import * from module.campaign.campaign_base import CampaignBase as CampaignBase_ -from module.campaign.campaign_ui import ModeSwitch +from module.campaign.campaign_ui import ASIDE_SWITCH_20241219, MODE_SWITCH_20241219 from module.logger import logger from module.map_detection.grid import Grid from module.template.assets import TEMPLATE_ENEMY_BOSS -MODE_SWITCH_20240725 = ModeSwitch('Mode_switch_20240725', is_selector=True, offset=(30, 30)) -MODE_SWITCH_20240725.add_status('combat', SWITCH_20240725_COMBAT, offset=(444, 4)) -MODE_SWITCH_20240725.add_status('story', SWITCH_20240725_STORY, offset=(444, 4)) - -CHAPTER_SWITCH_20241024 = ModeSwitch('Chapter_switch_20241024', is_selector=True, offset=(30, 30)) -CHAPTER_SWITCH_20241024.add_status('ab', CHAPTER_20241024_AB) -CHAPTER_SWITCH_20241024.add_status('cd', CHAPTER_20241024_CD) -CHAPTER_SWITCH_20241024.add_status('sp', CHAPTER_20241024_SP) -CHAPTER_SWITCH_20241024.add_status('ex', CHAPTER_20241024_EX) - class EventGrid(Grid): def predict_enemy_genre(self): @@ -43,33 +32,16 @@ class CampaignBase(CampaignBase_): """ ] - def campaign_set_chapter(self, name, mode='normal'): - """ - Args: - name (str): Campaign name, such as '7-2', 'd3', 'sp3'. - mode (str): 'normal' or 'hard'. - """ - chapter, stage = self._campaign_separate_name(name) - - if chapter in ['t']: + def campaign_set_chapter_20241219(self, chapter, stage, mode='combat'): + if chapter == 't': self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) + MODE_SWITCH_20241219.set('combat', main=self) if stage in ['1', '2', '3']: - CHAPTER_SWITCH_20241024.set('ab', main=self) + ASIDE_SWITCH_20241219.set('part1', main=self) elif stage in ['4', '5', '6']: - CHAPTER_SWITCH_20241024.set('cd', main=self) + ASIDE_SWITCH_20241219.set('part2', main=self) else: - logger.warning(f'Stage {name} is not in CHAPTER_SWITCH_20241024') + logger.warning(f'Stage {chapter}{stage} is not in event_20241024') self.campaign_ensure_chapter(index=chapter) - elif chapter in ['ex_sp']: - self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) - CHAPTER_SWITCH_20241024.set('sp', main=self) - self.campaign_ensure_chapter(index=chapter) - elif chapter in ['ex_ex']: - self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) - CHAPTER_SWITCH_20241024.set('ex', main=self) - self.campaign_ensure_chapter(index=chapter) - else: - logger.warning(f'Unknown campaign chapter: {name}') + + return super().campaign_set_chapter_20241219(chapter, stage, mode) diff --git a/campaign/event_20241024_cn/sp.py b/campaign/event_20241024_cn/sp.py index 57d0e8cdf..7e3c41f41 100644 --- a/campaign/event_20241024_cn/sp.py +++ b/campaign/event_20241024_cn/sp.py @@ -49,6 +49,7 @@ class Config: # ===== End of generated config ===== STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True HOMO_STORAGE = ((10, 5), [(152.845, 95.044), (1264.11, 95.044), (28.172, 525.208), (1419.191, 525.208)]) HOMO_EDGE_COLOR_RANGE = (0, 33) HOMO_EDGE_HOUGHLINES_THRESHOLD = 120 diff --git a/campaign/event_20241024_cn/t1.py b/campaign/event_20241024_cn/t1.py index 10a834cc1..8fdb54f95 100644 --- a/campaign/event_20241024_cn/t1.py +++ b/campaign/event_20241024_cn/t1.py @@ -58,6 +58,7 @@ class Config: # ===== End of generated config ===== STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { 'height': (80, 255 - 33), 'width': (0.9, 10), diff --git a/campaign/event_20241121_cn/campaign_base.py b/campaign/event_20241121_cn/campaign_base.py index a30f636ba..cbf57f182 100644 --- a/campaign/event_20241121_cn/campaign_base.py +++ b/campaign/event_20241121_cn/campaign_base.py @@ -1,6 +1,5 @@ -from campaign.event_20241024_cn.campaign_base import CHAPTER_SWITCH_20241024, MODE_SWITCH_20240725 from module.campaign.campaign_base import CampaignBase as CampaignBase_ -from module.logger import logger +from module.campaign.campaign_ui import MODE_SWITCH_20241219, ASIDE_SWITCH_20241219 class CampaignBase(CampaignBase_): @@ -22,41 +21,16 @@ class CampaignBase(CampaignBase_): return 1 return CampaignBase_._campaign_get_chapter_index(name) - def campaign_set_chapter(self, name, mode='normal'): - """ - Args: - name (str): Campaign name, such as '7-2', 'd3', 'sp3'. - mode (str): 'normal' or 'hard'. - """ - chapter, stage = self._campaign_separate_name(name) - logger.info([chapter, stage]) + def campaign_set_chapter_20241219(self, chapter, stage, mode='combat'): + if chapter == 't': + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('part1', main=self) + self.campaign_ensure_chapter(index=chapter) + if chapter == 'ttl': + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('part2', main=self) + self.campaign_ensure_chapter(index=chapter) - if chapter in ['t']: - self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) - if stage in ['1', '2', '3', '4', '5']: - CHAPTER_SWITCH_20241024.set('ab', main=self) - else: - logger.warning(f'Stage {name} is not in CHAPTER_SWITCH_20241024') - self.campaign_ensure_chapter(index=chapter) - elif chapter in ['ttl']: - self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) - if stage in ['1', '2', '3', '4', '5']: - CHAPTER_SWITCH_20241024.set('cd', main=self) - else: - logger.warning(f'Stage {name} is not in CHAPTER_SWITCH_20241024') - logger.info('campaign_ensure_chapter') - self.campaign_ensure_chapter(index=chapter) - elif chapter in ['ex_sp']: - self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) - CHAPTER_SWITCH_20241024.set('sp', main=self) - self.campaign_ensure_chapter(index=chapter) - elif chapter in ['ex_ex']: - self.ui_goto_event() - MODE_SWITCH_20240725.set('combat', main=self) - CHAPTER_SWITCH_20241024.set('ex', main=self) - self.campaign_ensure_chapter(index=chapter) - else: - logger.warning(f'Unknown campaign chapter: {name}') + return super().campaign_set_chapter_20241219(chapter, stage, mode) diff --git a/campaign/event_20241121_cn/sp.py b/campaign/event_20241121_cn/sp.py index 93f0b0eeb..c9b6560ab 100644 --- a/campaign/event_20241121_cn/sp.py +++ b/campaign/event_20241121_cn/sp.py @@ -58,6 +58,7 @@ class Config: # ===== End of generated config ===== STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True MAP_IS_ONE_TIME_STAGE = True INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { 'height': (80, 255 - 33), diff --git a/campaign/event_20241121_cn/t1.py b/campaign/event_20241121_cn/t1.py index 648a3b438..4b23c410b 100644 --- a/campaign/event_20241121_cn/t1.py +++ b/campaign/event_20241121_cn/t1.py @@ -58,6 +58,7 @@ class Config: # ===== End of generated config ===== STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { 'height': (80, 255 - 33), 'width': (0.9, 10), diff --git a/campaign/event_20241121_cn/ttl1.py b/campaign/event_20241121_cn/ttl1.py index 1d1ff592f..02c8ed322 100644 --- a/campaign/event_20241121_cn/ttl1.py +++ b/campaign/event_20241121_cn/ttl1.py @@ -45,6 +45,7 @@ class Config: # ===== End of generated config ===== STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True MAP_IS_ONE_TIME_STAGE = True INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { 'height': (80, 255 - 33), diff --git a/campaign/event_20241219_cn/a1.py b/campaign/event_20241219_cn/a1.py new file mode 100644 index 000000000..9210d6a5a --- /dev/null +++ b/campaign/event_20241219_cn/a1.py @@ -0,0 +1,99 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('A1') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + -- -- ME ++ ++ ++ -- ++ ++ + ++ ME -- MS -- MS -- ++ ++ + -- -- -- -- MB -- -- Me ++ + -- ME -- -- __ -- -- Me -- + ME -- ++ SP -- SP ++ ++ ++ + -- ME ++ -- -- -- -- ME -- + -- -- Me -- Me ++ -- -- 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 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, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1, 'boss': 1}, + {'battle': 4, 'enemy': 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, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_3(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/a2.py b/campaign/event_20241219_cn/a2.py new file mode 100644 index 000000000..e410acf79 --- /dev/null +++ b/campaign/event_20241219_cn/a2.py @@ -0,0 +1,79 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .a1 import Config as ConfigBase + +MAP = CampaignMap('A2') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- ME -- -- ME -- -- + -- -- ME -- ++ ME -- ME -- + ME -- -- ME -- -- -- ++ ++ + ++ -- Me ++ ++ ++ -- Me ++ + ++ -- -- MS MB MS -- -- -- + -- -- -- -- MS -- -- -- ME + -- ++ Me -- __ -- Me ++ -- + ++ ME -- SP -- SP -- 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 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, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, '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, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.108, 1.129) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.072, 1.092) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.041, 1.059) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/a3.py b/campaign/event_20241219_cn/a3.py new file mode 100644 index 000000000..d53d3e43b --- /dev/null +++ b/campaign/event_20241219_cn/a3.py @@ -0,0 +1,82 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .a1 import Config as ConfigBase + +MAP = CampaignMap('A3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- 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 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, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, '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, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/b1.py b/campaign/event_20241219_cn/b1.py new file mode 100644 index 000000000..21ee7f025 --- /dev/null +++ b/campaign/event_20241219_cn/b1.py @@ -0,0 +1,100 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('B1') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D6', 'H2', 'H6'] +MAP.camera_data_spawn_point = ['D2'] +MAP.map_data = """ + -- -- -- ++ SP -- SP ++ ++ ++ MB + -- ME ++ ++ -- __ -- -- Me -- -- + ME -- Me -- -- MS -- -- -- -- ME + -- -- -- -- MS -- MS Me -- ++ -- + -- ME ++ Me -- -- ++ ++ -- ME -- + -- -- ++ -- Me ++ ++ ++ -- -- -- + ++ -- ME -- -- ME -- ME -- -- ME + -- -- -- -- ++ -- 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 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, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2, 'boss': 1}, + {'battle': 5, 'enemy': 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() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.254, 1.278) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.213, 1.235) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.178, 1.199) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/b2.py b/campaign/event_20241219_cn/b2.py new file mode 100644 index 000000000..fbf267678 --- /dev/null +++ b/campaign/event_20241219_cn/b2.py @@ -0,0 +1,80 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .b1 import Config as ConfigBase + +MAP = CampaignMap('B2') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D5', 'H2', 'H5'] +MAP.camera_data_spawn_point = ['D5'] +MAP.map_data = """ + -- -- ME ++ ++ MB ++ ++ -- -- -- + -- ME -- ++ MS -- MS ++ Me ++ ++ + ME -- -- Me -- MS -- Me -- ++ ++ + ++ -- Me -- -- __ -- -- -- ME ME + ++ -- -- -- SP -- SP -- -- -- -- + ME -- ME -- ++ ++ ++ Me -- ++ -- + -- ++ ++ ME ++ -- ++ -- 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 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, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1, '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() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.168, 1.190) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.130, 1.151) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.097, 1.117) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/b3.py b/campaign/event_20241219_cn/b3.py new file mode 100644 index 000000000..6df3e57c7 --- /dev/null +++ b/campaign/event_20241219_cn/b3.py @@ -0,0 +1,84 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .b1 import Config as ConfigBase + +MAP = CampaignMap('B3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_covered = ['I3', 'I4'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- 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 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, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1, '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, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.135, 1.157) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.098, 1.118) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.066, 1.085) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_enemy(sort=('weight', 'cost_2', 'cost_1')): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/c1.py b/campaign/event_20241219_cn/c1.py new file mode 100644 index 000000000..e920799f7 --- /dev/null +++ b/campaign/event_20241219_cn/c1.py @@ -0,0 +1,99 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('C1') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + -- -- ME ++ ++ ++ -- ++ ++ + ++ ME -- MS -- MS -- ++ ++ + -- -- -- -- MB -- -- Me ++ + -- ME -- -- __ -- -- Me -- + ME -- ++ SP -- SP ++ ++ ++ + -- ME ++ -- -- -- -- ME -- + -- -- Me -- Me ++ -- -- 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 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, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, '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, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/c2.py b/campaign/event_20241219_cn/c2.py new file mode 100644 index 000000000..ce20b276d --- /dev/null +++ b/campaign/event_20241219_cn/c2.py @@ -0,0 +1,79 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .c1 import Config as ConfigBase + +MAP = CampaignMap('C2') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- ME -- -- ME -- -- + -- -- ME -- ++ ME -- ME -- + ME -- -- ME -- -- -- ++ ++ + ++ -- Me ++ ++ ++ -- Me ++ + ++ -- -- MS MB MS -- -- -- + -- -- -- -- MS -- -- -- ME + -- ++ Me -- __ -- Me ++ -- + ++ ME -- SP -- SP -- 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 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, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, '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, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.108, 1.129) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.072, 1.092) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.041, 1.059) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/c3.py b/campaign/event_20241219_cn/c3.py new file mode 100644 index 000000000..25c1cab33 --- /dev/null +++ b/campaign/event_20241219_cn/c3.py @@ -0,0 +1,83 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .c1 import Config as ConfigBase + +MAP = CampaignMap('C3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- 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 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, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'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, 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, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/campaign_base.py b/campaign/event_20241219_cn/campaign_base.py new file mode 100644 index 000000000..0740a4db9 --- /dev/null +++ b/campaign/event_20241219_cn/campaign_base.py @@ -0,0 +1,9 @@ +from module.campaign.campaign_base import CampaignBase as CampaignBase_ + + +class CampaignBase(CampaignBase_): + STAGE_INCREASE = [ + 'A1 > A2 > A3 > B1 > B2 > B3', + 'C1 > C2 > C3', + 'D1 > D2 > D3', + ] diff --git a/campaign/event_20241219_cn/d1.py b/campaign/event_20241219_cn/d1.py new file mode 100644 index 000000000..f3262f32c --- /dev/null +++ b/campaign/event_20241219_cn/d1.py @@ -0,0 +1,100 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('D1') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D6', 'H2', 'H6'] +MAP.camera_data_spawn_point = ['D2'] +MAP.map_data = """ + -- -- -- ++ SP -- SP ++ ++ ++ MB + -- ME ++ ++ -- __ -- -- Me -- -- + ME -- Me -- -- MS -- -- -- -- ME + -- -- -- -- MS -- MS Me -- ++ -- + -- ME ++ Me -- -- ++ ++ -- ME -- + -- -- ++ -- Me ++ ++ ++ -- -- -- + ++ -- ME -- -- ME -- ME -- -- ME + -- -- -- -- ++ -- 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 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, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1, '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() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.254, 1.278) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.213, 1.235) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.178, 1.199) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/d2.py b/campaign/event_20241219_cn/d2.py new file mode 100644 index 000000000..88c97a265 --- /dev/null +++ b/campaign/event_20241219_cn/d2.py @@ -0,0 +1,89 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .d1 import Config as ConfigBase + +MAP = CampaignMap('D2') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D5', 'H2', 'H5'] +MAP.camera_data_spawn_point = ['D5'] +MAP.map_data = """ + -- -- ME ++ ++ MB ++ ++ -- -- -- + -- ME -- ++ MS -- MS ++ Me ++ ++ + ME -- -- Me -- MS -- Me -- ++ ++ + ++ -- Me -- -- __ -- -- -- ME ME + ++ -- -- -- SP -- SP -- -- -- -- + ME -- ME -- ++ ++ ++ Me -- ++ -- + -- ++ ++ ME ++ -- ++ -- 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 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, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2, 'siren': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1}, + {'battle': 6, '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() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.168, 1.190) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.130, 1.151) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.097, 1.117) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + if self.clear_siren(): + return True + 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/event_20241219_cn/d3.py b/campaign/event_20241219_cn/d3.py new file mode 100644 index 000000000..cf5cf8d2e --- /dev/null +++ b/campaign/event_20241219_cn/d3.py @@ -0,0 +1,93 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .d1 import Config as ConfigBase + +MAP = CampaignMap('D3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_covered = ['I3', 'I4'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- 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 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 10 50 50 50 50 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2, 'siren': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1}, + {'battle': 6, '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, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.135, 1.157) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.098, 1.118) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.066, 1.085) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_enemy(sort=('weight', 'cost_2', 'cost_1')): + return True + + return self.battle_default() + + def battle_5(self): + if self.clear_siren(): + return True + if self.clear_enemy(sort=('weight', 'cost_2', 'cost_1')): + return True + + return self.battle_default() + + def battle_6(self): + return self.fleet_boss.clear_boss() diff --git a/module/base/base.py b/module/base/base.py index 7e9e0b674..b671fda64 100644 --- a/module/base/base.py +++ b/module/base/base.py @@ -124,14 +124,14 @@ class ModuleBase: return button - def appear(self, button, offset=0, interval=0, threshold=None): + def appear(self, button, offset=0, interval=0, similarity=0.85, threshold=10): """ Args: button (Button, Template, HierarchyButton, str): offset (bool, int): interval (int, float): interval between two active events. - threshold (int, float): 0 to 1 if use offset, bigger means more similar, - 0 to 255 if not use offset, smaller means more similar + similarity (int, float): 0 to 1. + threshold (int, float): 0 to 255 if not use offset, smaller means more similar Returns: bool: @@ -167,20 +167,51 @@ class ModuleBase: elif offset: if isinstance(offset, bool): offset = self.config.BUTTON_OFFSET - appear = button.match(self.device.image, offset=offset, - threshold=self.config.BUTTON_MATCH_SIMILARITY if threshold is None else threshold) + appear = button.match(self.device.image, offset=offset, similarity=similarity) else: - appear = button.appear_on(self.device.image, - threshold=self.config.COLOR_SIMILAR_THRESHOLD if threshold is None else threshold) + appear = button.appear_on(self.device.image, threshold=threshold) if appear and interval: self.interval_timer[button.name].reset() return appear - def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, threshold=None): + def match_template_color(self, button, offset=(20, 20), interval=0, similarity=0.85, threshold=30): + """ + Args: + button (Button): + offset (bool, int): + interval (int, float): interval between two active events. + similarity (int, float): 0 to 1. + threshold (int, float): 0 to 255 if not use offset, smaller means more similar + + Returns: + bool: + """ button = self.ensure_button(button) - appear = self.appear(button, offset=offset, interval=interval, threshold=threshold) + self.device.stuck_record_add(button) + + if interval: + if button.name in self.interval_timer: + if self.interval_timer[button.name].limit != interval: + self.interval_timer[button.name] = Timer(interval) + else: + self.interval_timer[button.name] = Timer(interval) + if not self.interval_timer[button.name].reached(): + return False + + appear = button.match_template_color( + self.device.image, offset=offset, similarity=similarity, threshold=threshold) + + if appear and interval: + self.interval_timer[button.name].reset() + + return appear + + def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, similarity=0.85, + threshold=30): + button = self.ensure_button(button) + appear = self.appear(button, offset=offset, interval=interval, similarity=similarity, threshold=threshold) if appear: if screenshot: self.device.sleep(self.config.WAIT_BEFORE_SAVING_SCREEN_SHOT) diff --git a/module/base/button.py b/module/base/button.py index c4fd82f3d..6c550b4de 100644 --- a/module/base/button.py +++ b/module/base/button.py @@ -198,13 +198,13 @@ class Button(Resource): self._match_binary_init = False self._match_luma_init = False - def match(self, image, offset=30, threshold=0.85): + def match(self, image, offset=30, similarity=0.85): """Detects button by template matching. To Some button, its location may not be static. Args: image: Screenshot. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. + similarity (float): 0-1. Similarity. Returns: bool. @@ -223,25 +223,25 @@ class Button(Resource): if self.is_gif: for template in self.image: res = cv2.matchTemplate(template, image, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - if similarity > threshold: + if sim > similarity: return True return False else: res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - return similarity > threshold + return sim > similarity - def match_binary(self, image, offset=30, threshold=0.85): + def match_binary(self, image, offset=30, similarity=0.85): """Detects button by template matching. To Some button, its location may not be static. This method will apply template matching under binarization. Args: image: Screenshot. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. + similarity (float): 0-1. Similarity. Returns: bool. @@ -266,9 +266,9 @@ class Button(Resource): _, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # template matching res = cv2.matchTemplate(template, image_binary, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - if similarity > threshold: + if sim > similarity: return True return False else: @@ -278,18 +278,18 @@ class Button(Resource): _, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # template matching res = cv2.matchTemplate(self.image_binary, image_binary, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - return similarity > threshold + return sim > similarity - def match_luma(self, image, offset=30, threshold=0.85): + def match_luma(self, image, offset=30, similarity=0.85): """ Detects button by template matching under Y channel (Luminance) Args: image: Screenshot. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. + similarity (float): 0-1. Similarity. Returns: bool. @@ -310,29 +310,37 @@ class Button(Resource): image_luma = rgb2luma(image) for template in self.image_luma: res = cv2.matchTemplate(template, image_luma, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - if similarity > threshold: + if sim > similarity: return True else: image_luma = rgb2luma(image) res = cv2.matchTemplate(self.image_luma, image_luma, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - return similarity > threshold + return sim > similarity - def match_appear_on(self, image, threshold=30): + def match_template_color(self, image, offset=(20, 20), similarity=0.85, threshold=30): """ + Template match first, color match then + Args: image: Screenshot. - threshold: Default to 10. + offset (int, tuple): Detection area offset. + similarity (float): 0-1. + threshold (int): Default to 30. Returns: - bool: + bool. """ - diff = np.subtract(self.button, self._button)[:2] - area = area_offset(self.area, offset=diff) - return color_similar(color1=get_color(image, area), color2=self.color, threshold=threshold) + if self.match(image, offset=offset, similarity=similarity): + diff = np.subtract(self.button, self._button)[:2] + area = area_offset(self.area, offset=diff) + color = get_color(image, area) + return color_similar(color1=color, color2=self.color, threshold=threshold) + else: + return False def crop(self, area, image=None, name=None): """ diff --git a/module/campaign/assets.py b/module/campaign/assets.py index da2b0fd71..50a8bcf6b 100644 --- a/module/campaign/assets.py +++ b/module/campaign/assets.py @@ -4,10 +4,10 @@ from module.base.template import Template # This file was automatically generated by dev_tools/button_extract.py. # Don't modify it manually. -CHAPTER_20241024_AB = Button(area={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, color={'cn': (162, 169, 196), 'en': (162, 169, 196), 'jp': (162, 169, 196), 'tw': (162, 169, 196)}, button={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_AB.png', 'en': './assets/cn/campaign/CHAPTER_20241024_AB.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_AB.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_AB.png'}) -CHAPTER_20241024_CD = Button(area={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, color={'cn': (168, 176, 204), 'en': (168, 176, 204), 'jp': (168, 176, 204), 'tw': (168, 176, 204)}, button={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_CD.png', 'en': './assets/cn/campaign/CHAPTER_20241024_CD.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_CD.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_CD.png'}) -CHAPTER_20241024_EX = Button(area={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, color={'cn': (169, 178, 207), 'en': (169, 178, 207), 'jp': (169, 178, 207), 'tw': (169, 178, 207)}, button={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_EX.png', 'en': './assets/cn/campaign/CHAPTER_20241024_EX.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_EX.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_EX.png'}) -CHAPTER_20241024_SP = Button(area={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, color={'cn': (163, 172, 201), 'en': (163, 172, 201), 'jp': (163, 172, 201), 'tw': (163, 172, 201)}, button={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_SP.png', 'en': './assets/cn/campaign/CHAPTER_20241024_SP.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_SP.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_SP.png'}) +CHAPTER_20241219_EX = Button(area={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, color={'cn': (169, 178, 207), 'en': (169, 178, 207), 'jp': (169, 178, 207), 'tw': (169, 178, 207)}, button={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_EX.png', 'en': './assets/cn/campaign/CHAPTER_20241219_EX.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_EX.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_EX.png'}) +CHAPTER_20241219_PART1 = Button(area={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, color={'cn': (162, 169, 196), 'en': (162, 169, 196), 'jp': (162, 169, 196), 'tw': (162, 169, 196)}, button={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_PART1.png', 'en': './assets/cn/campaign/CHAPTER_20241219_PART1.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_PART1.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_PART1.png'}) +CHAPTER_20241219_PART2 = Button(area={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, color={'cn': (168, 176, 204), 'en': (168, 176, 204), 'jp': (168, 176, 204), 'tw': (168, 176, 204)}, button={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_PART2.png', 'en': './assets/cn/campaign/CHAPTER_20241219_PART2.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_PART2.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_PART2.png'}) +CHAPTER_20241219_SP = Button(area={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, color={'cn': (163, 172, 201), 'en': (163, 172, 201), 'jp': (163, 172, 201), 'tw': (163, 172, 201)}, button={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_SP.png', 'en': './assets/cn/campaign/CHAPTER_20241219_SP.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_SP.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_SP.png'}) CHAPTER_NEXT = Button(area={'cn': (1216, 362, 1232, 388), 'en': (1216, 362, 1232, 388), 'jp': (1216, 362, 1232, 388), 'tw': (1216, 362, 1232, 388)}, color={'cn': (121, 150, 198), 'en': (121, 150, 198), 'jp': (121, 150, 198), 'tw': (121, 150, 198)}, button={'cn': (1216, 362, 1232, 388), 'en': (1216, 362, 1232, 388), 'jp': (1216, 362, 1232, 388), 'tw': (1216, 362, 1232, 388)}, file={'cn': './assets/cn/campaign/CHAPTER_NEXT.png', 'en': './assets/en/campaign/CHAPTER_NEXT.png', 'jp': './assets/jp/campaign/CHAPTER_NEXT.png', 'tw': './assets/tw/campaign/CHAPTER_NEXT.png'}) CHAPTER_PREV = Button(area={'cn': (42, 360, 58, 387), 'en': (42, 360, 58, 387), 'jp': (42, 360, 58, 387), 'tw': (42, 360, 58, 387)}, color={'cn': (105, 133, 169), 'en': (105, 133, 169), 'jp': (105, 133, 169), 'tw': (105, 133, 169)}, button={'cn': (42, 360, 58, 387), 'en': (42, 360, 58, 387), 'jp': (42, 360, 58, 387), 'tw': (42, 360, 58, 387)}, file={'cn': './assets/cn/campaign/CHAPTER_PREV.png', 'en': './assets/en/campaign/CHAPTER_PREV.png', 'jp': './assets/jp/campaign/CHAPTER_PREV.png', 'tw': './assets/tw/campaign/CHAPTER_PREV.png'}) COMMISSION_NOTICE_AT_CAMPAIGN = Button(area={'cn': (1077, 637, 1083, 643), 'en': (1077, 637, 1083, 643), 'jp': (1077, 637, 1083, 643), 'tw': (1077, 637, 1083, 643)}, color={'cn': (172, 72, 49), 'en': (172, 72, 49), 'jp': (172, 72, 49), 'tw': (172, 72, 49)}, button={'cn': (1077, 637, 1083, 643), 'en': (1077, 637, 1083, 643), 'jp': (1077, 637, 1083, 643), 'tw': (1077, 637, 1083, 643)}, file={'cn': './assets/cn/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png', 'en': './assets/en/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png', 'jp': './assets/jp/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png', 'tw': './assets/tw/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png'}) @@ -22,8 +22,8 @@ OCR_OIL_LIMIT = Button(area={'cn': (608, 0, 736, 19), 'en': (608, 0, 736, 19), ' REMOVE_SHIP = Button(area={'cn': (161, 148, 223, 211), 'en': (161, 148, 223, 211), 'jp': (161, 148, 223, 211), 'tw': (161, 148, 223, 211)}, color={'cn': (127, 128, 129), 'en': (127, 128, 129), 'jp': (127, 128, 129), 'tw': (127, 128, 129)}, button={'cn': (161, 148, 223, 211), 'en': (161, 148, 223, 211), 'jp': (161, 148, 223, 211), 'tw': (161, 148, 223, 211)}, file={'cn': './assets/cn/campaign/REMOVE_SHIP.png', 'en': './assets/cn/campaign/REMOVE_SHIP.png', 'jp': './assets/cn/campaign/REMOVE_SHIP.png', 'tw': './assets/cn/campaign/REMOVE_SHIP.png'}) SWITCH_1_HARD = Button(area={'cn': (82, 641, 148, 675), 'en': (87, 642, 148, 676), 'jp': (24, 645, 150, 697), 'tw': (82, 641, 148, 675)}, color={'cn': (233, 141, 128), 'en': (234, 139, 124), 'jp': (219, 116, 106), 'tw': (236, 159, 148)}, button={'cn': (82, 641, 148, 675), 'en': (87, 642, 148, 676), 'jp': (24, 645, 150, 697), 'tw': (82, 641, 148, 675)}, file={'cn': './assets/cn/campaign/SWITCH_1_HARD.png', 'en': './assets/en/campaign/SWITCH_1_HARD.png', 'jp': './assets/jp/campaign/SWITCH_1_HARD.png', 'tw': './assets/tw/campaign/SWITCH_1_HARD.png'}) SWITCH_1_NORMAL = Button(area={'cn': (80, 641, 148, 675), 'en': (79, 638, 147, 675), 'jp': (24, 644, 150, 697), 'tw': (79, 641, 148, 675)}, color={'cn': (157, 180, 227), 'en': (157, 180, 227), 'jp': (143, 169, 222), 'tw': (156, 179, 227)}, button={'cn': (80, 641, 148, 675), 'en': (79, 638, 147, 675), 'jp': (24, 644, 150, 697), 'tw': (79, 641, 148, 675)}, file={'cn': './assets/cn/campaign/SWITCH_1_NORMAL.png', 'en': './assets/en/campaign/SWITCH_1_NORMAL.png', 'jp': './assets/jp/campaign/SWITCH_1_NORMAL.png', 'tw': './assets/tw/campaign/SWITCH_1_NORMAL.png'}) -SWITCH_20240725_COMBAT = Button(area={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, color={'cn': (133, 96, 49), 'en': (133, 96, 49), 'jp': (133, 96, 49), 'tw': (133, 96, 49)}, button={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, file={'cn': './assets/cn/campaign/SWITCH_20240725_COMBAT.png', 'en': './assets/en/campaign/SWITCH_20240725_COMBAT.png', 'jp': './assets/jp/campaign/SWITCH_20240725_COMBAT.png', 'tw': './assets/tw/campaign/SWITCH_20240725_COMBAT.png'}) -SWITCH_20240725_STORY = Button(area={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, color={'cn': (105, 77, 31), 'en': (105, 77, 31), 'jp': (105, 77, 31), 'tw': (105, 77, 31)}, button={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, file={'cn': './assets/cn/campaign/SWITCH_20240725_STORY.png', 'en': './assets/en/campaign/SWITCH_20240725_STORY.png', 'jp': './assets/jp/campaign/SWITCH_20240725_STORY.png', 'tw': './assets/tw/campaign/SWITCH_20240725_STORY.png'}) +SWITCH_20241219_COMBAT = Button(area={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, color={'cn': (133, 96, 49), 'en': (133, 96, 49), 'jp': (133, 96, 49), 'tw': (133, 96, 49)}, button={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, file={'cn': './assets/cn/campaign/SWITCH_20241219_COMBAT.png', 'en': './assets/cn/campaign/SWITCH_20241219_COMBAT.png', 'jp': './assets/cn/campaign/SWITCH_20241219_COMBAT.png', 'tw': './assets/cn/campaign/SWITCH_20241219_COMBAT.png'}) +SWITCH_20241219_STORY = Button(area={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, color={'cn': (105, 77, 31), 'en': (105, 77, 31), 'jp': (105, 77, 31), 'tw': (105, 77, 31)}, button={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, file={'cn': './assets/cn/campaign/SWITCH_20241219_STORY.png', 'en': './assets/cn/campaign/SWITCH_20241219_STORY.png', 'jp': './assets/cn/campaign/SWITCH_20241219_STORY.png', 'tw': './assets/cn/campaign/SWITCH_20241219_STORY.png'}) SWITCH_2_EX = Button(area={'cn': (272, 658, 310, 676), 'en': (251, 644, 313, 697), 'jp': (186, 638, 314, 692), 'tw': (241, 640, 312, 692)}, color={'cn': (253, 168, 98), 'en': (254, 163, 80), 'jp': (205, 136, 64), 'tw': (254, 161, 72)}, button={'cn': (272, 658, 310, 676), 'en': (251, 644, 313, 697), 'jp': (186, 638, 314, 692), 'tw': (241, 640, 312, 692)}, file={'cn': './assets/cn/campaign/SWITCH_2_EX.png', 'en': './assets/en/campaign/SWITCH_2_EX.png', 'jp': './assets/jp/campaign/SWITCH_2_EX.png', 'tw': './assets/tw/campaign/SWITCH_2_EX.png'}) SWITCH_2_HARD = Button(area={'cn': (246, 641, 311, 675), 'en': (244, 640, 312, 684), 'jp': (233, 655, 310, 681), 'tw': (245, 641, 311, 674)}, color={'cn': (233, 140, 127), 'en': (228, 121, 106), 'jp': (223, 110, 96), 'tw': (237, 161, 150)}, button={'cn': (246, 641, 311, 675), 'en': (244, 640, 312, 684), 'jp': (233, 655, 310, 681), 'tw': (245, 641, 311, 674)}, file={'cn': './assets/cn/campaign/SWITCH_2_HARD.png', 'en': './assets/en/campaign/SWITCH_2_HARD.png', 'jp': './assets/jp/campaign/SWITCH_2_HARD.png', 'tw': './assets/tw/campaign/SWITCH_2_HARD.png'}) TEMPLATE_EVENT_20230817_STORY_E1 = Template(file={'cn': './assets/cn/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png', 'en': './assets/en/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png', 'jp': './assets/jp/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png', 'tw': './assets/tw/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png'}) diff --git a/module/campaign/campaign_ui.py b/module/campaign/campaign_ui.py index 8da38f93a..32e424b16 100644 --- a/module/campaign/campaign_ui.py +++ b/module/campaign/campaign_ui.py @@ -18,11 +18,22 @@ class ModeSwitch(Switch): MODE_SWITCH_1 = ModeSwitch('Mode_switch_1', offset=(30, 10)) -MODE_SWITCH_1.add_status('normal', SWITCH_1_NORMAL) -MODE_SWITCH_1.add_status('hard', SWITCH_1_HARD) +MODE_SWITCH_1.add_state('normal', SWITCH_1_NORMAL) +MODE_SWITCH_1.add_state('hard', SWITCH_1_HARD) MODE_SWITCH_2 = ModeSwitch('Mode_switch_2', offset=(30, 10)) -MODE_SWITCH_2.add_status('hard', SWITCH_2_HARD) -MODE_SWITCH_2.add_status('ex', SWITCH_2_EX) +MODE_SWITCH_2.add_state('hard', SWITCH_2_HARD) +MODE_SWITCH_2.add_state('ex', SWITCH_2_EX) + +# Event mode switches changing from 20240725 to 20241219 +# I think it stable at 20241219, so give them names with date 20241219 +MODE_SWITCH_20241219 = ModeSwitch('Mode_switch_20241219', is_selector=True, offset=(30, 30)) +MODE_SWITCH_20241219.add_state('combat', SWITCH_20241219_COMBAT) +MODE_SWITCH_20241219.add_state('story', SWITCH_20241219_STORY) +ASIDE_SWITCH_20241219 = ModeSwitch('Aside_switch_20241219', is_selector=True, offset=(30, 30)) +ASIDE_SWITCH_20241219.add_state('part1', CHAPTER_20241219_PART1) +ASIDE_SWITCH_20241219.add_state('part2', CHAPTER_20241219_PART2) +ASIDE_SWITCH_20241219.add_state('sp', CHAPTER_20241219_SP) +ASIDE_SWITCH_20241219.add_state('ex', CHAPTER_20241219_EX) class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): @@ -83,9 +94,6 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): """ Args: mode (str): 'normal', 'hard', 'ex' - - Returns: - bool: If mode changed. """ if mode == 'hard': self.config.override(Campaign_Mode='hard') @@ -113,6 +121,34 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): else: logger.warning(f'Unknown campaign mode: {mode}') + def campaign_ensure_mode_20241219(self, mode='combat'): + """ + Args: + mode (str): 'combat' or 'story' + """ + if mode in ['normal', 'hard', 'ex', 'combat']: + MODE_SWITCH_20241219.set('combat', main=self) + elif mode in ['story']: + MODE_SWITCH_20241219.set('story', main=self) + else: + logger.warning(f'Unknown campaign mode: {mode}') + + def campaign_ensure_aside_20241219(self, chapter): + """ + Args: + chapter: 'part1', 'part2', 'sp', 'ex' + """ + if chapter in ['part1', 'a', 'c', 't']: + MODE_SWITCH_20241219.set('part1', main=self) + elif chapter in ['part2', 'b', 'd']: + MODE_SWITCH_20241219.set('part2', main=self) + elif chapter in ['sp', 'ex_sp']: + MODE_SWITCH_20241219.set('sp', main=self) + elif chapter in ['ex', 'ex_ex']: + MODE_SWITCH_20241219.set('sp', main=self) + else: + logger.warning(f'Unknown campaign aside: {chapter}') + def campaign_get_mode_names(self, name): """ Get stage names in both 'normal' and 'hard' @@ -136,6 +172,22 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): return [f'b{name[1:]}', f'd{name[1:]}'] return [name] + def _campaign_name_is_hard(self, name): + """ + Reuse manual defination in campaign_get_mode_names() + + Args: + name: 'a1', 'ht1', 'sp1' + + Returns: + bool: If stage is hard mode + """ + mode_names = self.campaign_get_mode_names(name) + if len(mode_names) == 2 and mode_names[1] == name: + return True + else: + return False + def campaign_get_entrance(self, name): """ Args: @@ -195,16 +247,55 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): else: return False + def campaign_set_chapter_20241219(self, chapter, stage, mode='combat'): + if not self.config.MAP_CHAPTER_SWITCH_20241219: + return False + + if self._campaign_name_is_hard(f'{chapter}{stage}'): + self.config.override(Campaign_Mode='hard') + + if mode == 'story': + MODE_SWITCH_20241219.set('story', main=self) + return True + if chapter in ['a', 'c', 't']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('part1', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + if chapter in ['b', 'd', 'ttl']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('part2', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + if chapter in ['ex_sp']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('sp', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + if chapter in ['ex_ex']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('ex', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + else: + return False + def campaign_set_chapter(self, name, mode='normal'): """ Args: name (str): Campaign name, such as '7-2', 'd3', 'sp3'. mode (str): 'normal' or 'hard'. """ - chapter, _ = self._campaign_separate_name(name) + chapter, stage = self._campaign_separate_name(name) if self.campaign_set_chapter_main(chapter, mode): pass + elif self.campaign_set_chapter_20241219(chapter, stage, mode): + pass elif self.campaign_set_chapter_event(chapter, mode): pass elif self.campaign_set_chapter_sp(chapter, mode): diff --git a/module/campaign/run.py b/module/campaign/run.py index ae4f105f6..2f124a861 100644 --- a/module/campaign/run.py +++ b/module/campaign/run.py @@ -2,7 +2,6 @@ import copy import importlib import os import random -import re from module.campaign.campaign_base import CampaignBase from module.campaign.campaign_event import CampaignEvent @@ -172,7 +171,6 @@ class CampaignRun(CampaignEvent, ShopStatus): Returns: str, str: name, folder """ - name = re.sub('[ \t\n]', '', str(name)).lower() name = to_map_file_name(name) # For GemsFarming, auto choose events or main chapters if self.config.task.command == 'GemsFarming': diff --git a/module/coalition/ui.py b/module/coalition/ui.py index 7fd2ee01a..df7490f41 100644 --- a/module/coalition/ui.py +++ b/module/coalition/ui.py @@ -24,12 +24,12 @@ class CoalitionUI(Combat): """ MODE_SWITCH = Switch('CoalitionMode', offset=(20, 20)) if event == 'coalition_20230323': - MODE_SWITCH.add_status('story', FROSTFALL_MODE_STORY) - MODE_SWITCH.add_status('battle', FROSTFALL_MODE_BATTLE) + MODE_SWITCH.add_state('story', FROSTFALL_MODE_STORY) + MODE_SWITCH.add_state('battle', FROSTFALL_MODE_BATTLE) elif event == 'coalition_20240627': # Note that switch button are reversed - MODE_SWITCH.add_status('story', ACADEMY_MODE_BATTLE) - MODE_SWITCH.add_status('battle', ACADEMY_MODE_STORY) + MODE_SWITCH.add_state('story', ACADEMY_MODE_BATTLE) + MODE_SWITCH.add_state('battle', ACADEMY_MODE_STORY) else: logger.error(f'MODE_SWITCH is not defined in event {event}') raise ScriptError @@ -52,11 +52,11 @@ class CoalitionUI(Combat): """ FLEET_SWITCH = Switch('FleetMode', is_selector=True, offset=0) # No offset for color match if event == 'coalition_20230323': - FLEET_SWITCH.add_status('single', FROSTFALL_SWITCH_SINGLE) - FLEET_SWITCH.add_status('multi', FROSTFALL_SWITCH_MULTI) + FLEET_SWITCH.add_state('single', FROSTFALL_SWITCH_SINGLE) + FLEET_SWITCH.add_state('multi', FROSTFALL_SWITCH_MULTI) elif event == 'coalition_20240627': - FLEET_SWITCH.add_status('single', ACADEMY_SWITCH_SINGLE) - FLEET_SWITCH.add_status('multi', ACADEMY_SWITCH_MULTI) + FLEET_SWITCH.add_state('single', ACADEMY_SWITCH_SINGLE) + FLEET_SWITCH.add_state('multi', ACADEMY_SWITCH_MULTI) else: logger.error(f'FLEET_SWITCH is not defined in event {event}') raise ScriptError diff --git a/module/combat/combat.py b/module/combat/combat.py index e35efac73..c703f5b48 100644 --- a/module/combat/combat.py +++ b/module/combat/combat.py @@ -1,21 +1,21 @@ import numpy as np from module.base.timer import Timer -from module.base.utils import get_color, color_similar +from module.base.utils import color_similar, get_color from module.combat.assets import * -from module.combat_ui.assets import * from module.combat.combat_auto import CombatAuto from module.combat.combat_manual import CombatManual from module.combat.hp_balancer import HPBalancer from module.combat.level import Level from module.combat.submarine import SubmarineCall +from module.combat_ui.assets import * from module.handler.auto_search import AutoSearchHandler from module.logger import logger from module.map.assets import MAP_OFFENSIVE from module.retire.retirement import Retirement from module.statistics.azurstats import DropImage from module.template.assets import TEMPLATE_COMBAT_LOADING -from module.ui.assets import BACK_ARROW, MUNITIONS_CHECK +from module.ui.assets import BACK_ARROW, EXERCISE_CHECK, MUNITIONS_CHECK class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatManual, AutoSearchHandler): @@ -423,7 +423,12 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan Returns: bool: """ - if self.appear(MUNITIONS_CHECK, offset=(20, 20), interval=2): + if self.appear(MUNITIONS_CHECK, offset=(20, 20), interval=5): + logger.info(f'{MUNITIONS_CHECK} -> {BACK_ARROW}') + self.device.click(BACK_ARROW) + return True + if self.appear(EXERCISE_CHECK, offset=(20, 20), interval=5): + logger.info(f'{EXERCISE_CHECK} -> {BACK_ARROW}') self.device.click(BACK_ARROW) return True diff --git a/module/commission/assets.py b/module/commission/assets.py index 282e65e95..703630296 100644 --- a/module/commission/assets.py +++ b/module/commission/assets.py @@ -8,7 +8,7 @@ COMMISSION_ADVICE = Button(area={'cn': (871, 322, 999, 383), 'en': (871, 328, 10 COMMISSION_DAILY = Button(area={'cn': (35, 132, 67, 186), 'en': (30, 126, 75, 188), 'jp': (17, 168, 82, 185), 'tw': (35, 132, 67, 186)}, color={'cn': (208, 172, 118), 'en': (170, 132, 92), 'jp': (148, 115, 76), 'tw': (208, 171, 119)}, button={'cn': (35, 132, 67, 186), 'en': (30, 126, 75, 188), 'jp': (17, 168, 82, 185), 'tw': (35, 132, 67, 186)}, file={'cn': './assets/cn/commission/COMMISSION_DAILY.png', 'en': './assets/en/commission/COMMISSION_DAILY.png', 'jp': './assets/jp/commission/COMMISSION_DAILY.png', 'tw': './assets/tw/commission/COMMISSION_DAILY.png'}) COMMISSION_HAS_PENDING = Button(area={'cn': (320, 288, 380, 338), 'en': (320, 288, 380, 338), 'jp': (320, 288, 380, 338), 'tw': (320, 288, 380, 338)}, color={'cn': (121, 113, 152), 'en': (121, 113, 152), 'jp': (121, 113, 152), 'tw': (121, 113, 152)}, button={'cn': (320, 288, 380, 338), 'en': (320, 288, 380, 338), 'jp': (320, 288, 380, 338), 'tw': (320, 288, 380, 338)}, file={'cn': './assets/cn/commission/COMMISSION_HAS_PENDING.png', 'en': './assets/en/commission/COMMISSION_HAS_PENDING.png', 'jp': './assets/jp/commission/COMMISSION_HAS_PENDING.png', 'tw': './assets/tw/commission/COMMISSION_HAS_PENDING.png'}) COMMISSION_SCROLL_AREA = Button(area={'cn': (1254, 77, 1261, 676), 'en': (1254, 77, 1261, 676), 'jp': (1254, 77, 1261, 676), 'tw': (1254, 77, 1261, 676)}, color={'cn': (213, 183, 66), 'en': (213, 183, 66), 'jp': (213, 183, 66), 'tw': (213, 183, 66)}, button={'cn': (1254, 77, 1261, 676), 'en': (1254, 77, 1261, 676), 'jp': (1254, 77, 1261, 676), 'tw': (1254, 77, 1261, 676)}, file={'cn': './assets/cn/commission/COMMISSION_SCROLL_AREA.png', 'en': './assets/en/commission/COMMISSION_SCROLL_AREA.png', 'jp': './assets/jp/commission/COMMISSION_SCROLL_AREA.png', 'tw': './assets/tw/commission/COMMISSION_SCROLL_AREA.png'}) -COMMISSION_START = Button(area={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1062, 342, 1125, 374), 'tw': (1027, 326, 1157, 389)}, color={'cn': (229, 175, 113), 'en': (236, 197, 150), 'jp': (237, 201, 153), 'tw': (231, 180, 120)}, button={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1028, 336, 1157, 398), 'tw': (1027, 326, 1157, 389)}, file={'cn': './assets/cn/commission/COMMISSION_START.png', 'en': './assets/en/commission/COMMISSION_START.png', 'jp': './assets/jp/commission/COMMISSION_START.png', 'tw': './assets/tw/commission/COMMISSION_START.png'}) +COMMISSION_START = Button(area={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1033, 340, 1153, 376), 'tw': (1027, 326, 1157, 389)}, color={'cn': (229, 175, 113), 'en': (236, 197, 150), 'jp': (231, 184, 121), 'tw': (231, 180, 120)}, button={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1033, 340, 1153, 376), 'tw': (1027, 326, 1157, 389)}, file={'cn': './assets/cn/commission/COMMISSION_START.png', 'en': './assets/en/commission/COMMISSION_START.png', 'jp': './assets/jp/commission/COMMISSION_START.png', 'tw': './assets/tw/commission/COMMISSION_START.png'}) COMMISSION_URGENT = Button(area={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 283), 'jp': (34, 266, 68, 279), 'tw': (35, 229, 69, 280)}, color={'cn': (215, 188, 124), 'en': (169, 138, 95), 'jp': (216, 190, 111), 'tw': (213, 186, 123)}, button={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 283), 'jp': (34, 266, 68, 279), 'tw': (35, 229, 69, 280)}, file={'cn': './assets/cn/commission/COMMISSION_URGENT.png', 'en': './assets/en/commission/COMMISSION_URGENT.png', 'jp': './assets/jp/commission/COMMISSION_URGENT.png', 'tw': './assets/tw/commission/COMMISSION_URGENT.png'}) EXP_INFO_S_REWARD = Button(area={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, color={'cn': (233, 241, 127), 'en': (89, 115, 159), 'jp': (233, 241, 127), 'tw': (233, 241, 127)}, button={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, file={'cn': './assets/cn/commission/EXP_INFO_S_REWARD.png', 'en': './assets/en/commission/EXP_INFO_S_REWARD.png', 'jp': './assets/jp/commission/EXP_INFO_S_REWARD.png', 'tw': './assets/tw/commission/EXP_INFO_S_REWARD.png'}) REWARD_1 = Button(area={'cn': (383, 285, 503, 297), 'en': (403, 274, 504, 290), 'jp': (432, 273, 476, 294), 'tw': (383, 285, 503, 297)}, color={'cn': (238, 168, 81), 'en': (241, 198, 145), 'jp': (241, 188, 122), 'tw': (238, 168, 81)}, button={'cn': (383, 285, 503, 297), 'en': (392, 262, 515, 303), 'jp': (393, 262, 514, 303), 'tw': (383, 285, 503, 297)}, file={'cn': './assets/cn/commission/REWARD_1.png', 'en': './assets/en/commission/REWARD_1.png', 'jp': './assets/jp/commission/REWARD_1.png', 'tw': './assets/tw/commission/REWARD_1.png'}) diff --git a/module/commission/commission.py b/module/commission/commission.py index 390e3ff90..c1f657d56 100644 --- a/module/commission/commission.py +++ b/module/commission/commission.py @@ -24,8 +24,8 @@ from module.ui.ui import UI from module.ui_white.assets import REWARD_1_WHITE, REWARD_GOTO_COMMISSION_WHITE COMMISSION_SWITCH = Switch('Commission_switch', is_selector=True) -COMMISSION_SWITCH.add_status('daily', COMMISSION_DAILY) -COMMISSION_SWITCH.add_status('urgent', COMMISSION_URGENT) +COMMISSION_SWITCH.add_state('daily', COMMISSION_DAILY) +COMMISSION_SWITCH.add_state('urgent', COMMISSION_URGENT) COMMISSION_SCROLL = Scroll(COMMISSION_SCROLL_AREA, color=(247, 211, 66), name='COMMISSION_SCROLL') @@ -363,7 +363,8 @@ class RewardCommission(UI, InfoHandler): raise GameStuckError('Triggered commission list flashing bug') # Click - if self.appear_then_click(COMMISSION_START, offset=(5, 20), interval=7): + if self.match_template_color(COMMISSION_START, offset=(5, 20), interval=7): + self.device.click(COMMISSION_START) self.interval_reset(COMMISSION_ADVICE) comm_timer.reset() continue diff --git a/module/commission/project.py b/module/commission/project.py index babd46897..41fff2c42 100644 --- a/module/commission/project.py +++ b/module/commission/project.py @@ -36,6 +36,14 @@ class SuffixOcr(Ocr): if len(left): image = image[:, left[-1] - look_back:] + if server.server in ['jp']: + # slice top and bottom part to get clearer roman digits + # will need to pad white background for better recognization + image = image[8:-10, :] + cv2.normalize(image, image, -55, 255, cv2.NORM_MINMAX) + image = (image > 80).astype(np.uint8) * 255 + image = np.pad(image, ((0, 1), (0, 0)), mode='edge') + image = np.pad(image, ((4, 3), (0, 0)), mode='constant', constant_values=255) return image @@ -167,7 +175,7 @@ class Commission: self.genre = self.commission_name_parse(self.name) # Suffix - ocr = SuffixOcr(button, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='IV') + ocr = SuffixOcr(button, lang='azur_lane', letter=(201, 201, 201), threshold=128, alphabet='IV') self.suffix = self.beautify_name(ocr.ocr(self.image)) # Duration time diff --git a/module/config/argument/args.json b/module/config/argument/args.json index b2b5c8a15..f75a16d13 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1934,17 +1934,18 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "display": "hide", "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -5043,16 +5044,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -5955,16 +5957,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -7373,16 +7376,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -7846,16 +7850,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -8319,16 +8324,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -8792,16 +8798,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -9255,16 +9262,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20240229_cn", - "event_20241121_cn" + "event_20231221_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", + "tw": "event_20231221_cn" }, "Mode": { "type": "select", diff --git a/module/config/config_manual.py b/module/config/config_manual.py index 755a19116..14c5b62ab 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -43,9 +43,7 @@ class ManualConfig: """ module.base """ - COLOR_SIMILAR_THRESHOLD = 10 BUTTON_OFFSET = 30 - BUTTON_MATCH_SIMILARITY = 0.85 WAIT_BEFORE_SAVING_SCREEN_SHOT = 1 """ @@ -118,6 +116,8 @@ class ManualConfig: module.map.fleet """ MAP_HAS_MODE_SWITCH = False # event_20240725_cn has mode switch in map preparation + # Events from 20240725 to 20241219 introduced new chapter switches + MAP_CHAPTER_SWITCH_20241219 = False MAP_HAS_CLEAR_PERCENTAGE = True MAP_HAS_WALK_SPEEDUP = False MAP_HAS_AMBUSH = True diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 4ef238a96..7d6d32bd8 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -791,6 +791,7 @@ "event_20240912_cn": "Ode of Everblooming Crimson", "event_20241024_cn": "Tempesta and the Sleeping Sea", "event_20241121_cn": "Dangerous Inventions Incoming", + "event_20241219_cn": "Substellar Crepuscule", "raid_20200624": "Air Raid Drills with Essex Rerun", "raid_20210708": "Cross Wave rerun", "raid_20220127": "Mystery Investigation", @@ -2970,8 +2971,8 @@ "help": "At the beginning of each month OpSi is reset\nThe following must be satisfied:\n- OpSi story and practice battles must be complete\nEach zone will be visited in a clockwise direction every 27 minutes until world has been been completely opened\nNo need to consume 5000 oil for special radar in OpSi voucher shop" }, "SpecialRadar": { - "name": "SpecialRadar Bought", - "help": "Enable if you have purchased the special radar\nAllows Alas to explore OpSi continously without having to wait 27 minutes between each zone clear\nCannot be selected when not purchased and used manually" + "name": "OpSi Data Logger Bought (5k oil item)", + "help": "Purchase Operation Siren Data Logger is not a must, enable if you have purchased.\nAllows Alas to explore OpSi continously without having to wait 27 minutes between each zone clear\nCannot be selected when not purchased and used manually" }, "ForceRun": { "name": "Force Run", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 02ede3a23..93ad3a31d 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -791,6 +791,7 @@ "event_20240912_cn": "絳染む丹華の詠歌", "event_20241024_cn": "テンペスタと眠りし海", "event_20241121_cn": "危険発明接近中", + "event_20241219_cn": "星降る夕影の残光", "raid_20200624": "特別演習超空強襲波(復刻)", "raid_20210708": "交錯する新たな波 (復刻)", "raid_20220127": "秘密事件調査", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 51b46ef72..6c55af08e 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -791,6 +791,7 @@ "event_20240912_cn": "唤醒苍红之炎", "event_20241024_cn": "飓风与沉眠之海", "event_20241121_cn": "危险发明迫近中", + "event_20241219_cn": "星光下的余晖", "raid_20200624": "复刻特别演习埃塞克斯级", "raid_20210708": "复刻穿越彼方的水线", "raid_20220127": "演习神秘事件调查", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 61179fd1f..66c85c7dd 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -781,7 +781,7 @@ "event_20230914_cn": "須臾望月抄", "event_20231026_cn": "飓風與青春之泉", "event_20231123_cn": "蒼閃忍法帖", - "event_20231221_cn": "Light-Chasing Sea of Stars", + "event_20231221_cn": "星海逐光", "event_20240229_cn": "雪境迷蹤", "event_20240425_cn": "共鳴的PASSION", "event_20240521_cn": "Light of the Martyrium", @@ -790,7 +790,8 @@ "event_20240829_cn": "埋葬於彼岸之花", "event_20240912_cn": "Ode of Everblooming Crimson", "event_20241024_cn": "Tempesta and the Sleeping Sea", - "event_20241121_cn": "Dangerous Inventions Incoming", + "event_20241121_cn": "危險發明逼近中", + "event_20241219_cn": "Substellar Crepuscule", "raid_20200624": "特別演習埃塞克斯級(復刻)", "raid_20210708": "復刻穿越彼方的水線", "raid_20220127": "演習神秘事件調查", diff --git a/module/device/device.py b/module/device/device.py index 21d38903b..b39aebd24 100644 --- a/module/device/device.py +++ b/module/device/device.py @@ -146,13 +146,22 @@ class Device(Screenshot, Control, AppControl): # self.config.Emulator_ControlMethod = 'minitouch' # Allow Hermit on VMOS only if self.config.Emulator_ControlMethod == 'Hermit' and not self.is_vmos: - logger.warning('ControlMethod is allowed on VMOS only') + logger.warning('ControlMethod Hermit is allowed on VMOS only') self.config.Emulator_ControlMethod = 'MaaTouch' if self.config.Emulator_ScreenshotMethod == 'ldopengl' \ and self.config.Emulator_ControlMethod == 'minitouch': logger.warning('Use MaaTouch on ldplayer') self.config.Emulator_ControlMethod = 'MaaTouch' - pass + + # Fallback to auto if nemu_ipc and ldopengl are selected on non-corresponding emulators + if self.config.Emulator_ScreenshotMethod == 'nemu_ipc': + if not (self.is_emulator and self.is_mumu_family): + logger.warning('ScreenshotMethod nemu_ipc is available on MuMu Player 12 only, fallback to auto') + self.config.Emulator_ScreenshotMethod = 'auto' + if self.config.Emulator_ScreenshotMethod == 'ldopengl': + if not (self.is_emulator and self.is_ldplayer_bluestacks_family): + logger.warning('ScreenshotMethod ldopengl is available on LD Player only, fallback to auto') + self.config.Emulator_ScreenshotMethod = 'auto' def handle_night_commission(self, daily_trigger='21:00', threshold=30): """ diff --git a/module/dorm/buy_furniture.py b/module/dorm/buy_furniture.py index 4d2061d38..8bc58a1a2 100644 --- a/module/dorm/buy_furniture.py +++ b/module/dorm/buy_furniture.py @@ -37,11 +37,11 @@ class BuyFurniture(UI): # Enter furniture shop page from page_dorm, only need to enter once if self.appear(DORM_CHECK, offset=(20, 20), interval=3): self.device.click(DORM_FURNITURE_SHOP_ENTER) - self.interval_reset(GET_SHIP) + self.interval_reset([GET_SHIP, EXERCISE_PREPARATION]) continue if self.appear(DORM_FURNITURE_SHOP_FIRST_SELECTED, offset=(20, 20)): - self.interval_reset(EXERCISE_PREPARATION) + self.interval_reset([GET_SHIP, EXERCISE_PREPARATION]) # Enter furniture details page from furniture shop page if self.appear(DORM_FURNITURE_DETAILS_ENTER, offset=(20, 20), interval=3): self.device.click(DORM_FURNITURE_DETAILS_ENTER) @@ -50,6 +50,7 @@ class BuyFurniture(UI): # Re select the first piece of furniture on left side of furniture list below. elif self.appear(DORM_FURNITURE_SHOP_FIRST, offset=(20, 20), interval=3): self.device.click(DORM_FURNITURE_SHOP_FIRST) + self.interval_reset([GET_SHIP, EXERCISE_PREPARATION]) continue if self.appear(DORM_FURNITURE_DETAILS_QUIT, offset=(20, 20)): @@ -183,8 +184,7 @@ class BuyFurniture(UI): False if Failed buy """ self.enter_first_furniture_details_page() - if self.appear(DORM_FURNITURE_COUNTDOWN, offset=(20, 20)) \ - and DORM_FURNITURE_COUNTDOWN.match_appear_on(self.device.image): + if self.match_template_color(DORM_FURNITURE_COUNTDOWN, offset=(20, 20)): logger.info("There is a time-limited furniture available for buy") if self.buy_furniture_once(self.config.BuyFurniture_BuyOption): diff --git a/module/equipment/equipment.py b/module/equipment/equipment.py index e5b5adb09..41cf30321 100644 --- a/module/equipment/equipment.py +++ b/module/equipment/equipment.py @@ -434,6 +434,7 @@ class EquipmentNew(StorageHandler): @equip_assets_override("new") def ship_equipment_take_off(self, skip_first_screenshot=True): + logger.info('Equipment take off') bar_timer = Timer(5) off_timer = Timer(5) confirm_timer = Timer(5) @@ -455,11 +456,14 @@ class EquipmentNew(StorageHandler): if confirm_timer.reached() and self.handle_popup_confirm(): confirm_timer.reset() + off_timer.reset() + bar_timer.reset() continue if off_timer.reached(): if not self.info_bar_count() and self.appear_then_click(EQUIP_OFF, offset=(20, 20)): off_timer.reset() + bar_timer.reset() continue if bar_timer.reached(): @@ -468,7 +472,8 @@ class EquipmentNew(StorageHandler): bar_timer.reset() continue - @equip_assets_override("new") + logger.info('Equipment take off ended') + def fleet_equipment_take_off(self, enter, long_click, out): """ Args: @@ -490,6 +495,7 @@ class EquipmentNew(StorageHandler): @equip_assets_override("new") def ship_equipment_take_on_preset(self, index, skip_first_screenshot=True): + logger.info('Equipment take on preset') bar_timer = Timer(5) on_timer = Timer(5) @@ -520,9 +526,11 @@ class EquipmentNew(StorageHandler): self.device.click(EQUIP_3) on_timer.reset() + bar_timer.reset() continue - @equip_assets_override("new") + logger.info('Equipment take on ended') + def fleet_equipment_take_on_preset(self, preset_record, enter, long_click, out): """ Args: diff --git a/module/equipment/equipment_change.py b/module/equipment/equipment_change.py index 918298758..1b6934c5e 100644 --- a/module/equipment/equipment_change.py +++ b/module/equipment/equipment_change.py @@ -20,8 +20,8 @@ EQUIPMENT_SCROLL = Scroll(EQUIP_SCROLL, color=(247, 211, 66), name='EQUIP_SCROLL SIM_VALUE = 0.90 equipping_filter = Switch('Equipping_filter') -equipping_filter.add_status('on', check_button=EQUIPPING_ON) -equipping_filter.add_status('off', check_button=EQUIPPING_OFF) +equipping_filter.add_state('on', check_button=EQUIPPING_ON) +equipping_filter.add_state('off', check_button=EQUIPPING_OFF) class EquipmentChangeOld(Equipment): diff --git a/module/exercise/combat.py b/module/exercise/combat.py index ef649c92b..85cc6509b 100644 --- a/module/exercise/combat.py +++ b/module/exercise/combat.py @@ -60,6 +60,8 @@ class ExerciseCombat(HpDaemon, OpponentChoose, ExerciseEquipment, Combat): p = self.is_combat_executing() if p: + if end: + end = False if pause is None: pause = p else: diff --git a/module/freebies/battle_pass.py b/module/freebies/battle_pass.py index c9986c1cc..75dadff99 100644 --- a/module/freebies/battle_pass.py +++ b/module/freebies/battle_pass.py @@ -6,6 +6,7 @@ from module.logger import logger from module.ui.assets import BATTLE_PASS_CHECK, REWARD_GOTO_BATTLE_PASS from module.ui.page import page_reward from module.ui.ui import UI +from module.ui_white.assets import POPUP_CONFIRM_WHITE_BATTLEPASS class BattlePass(Combat, UI): @@ -72,8 +73,7 @@ class BattlePass(Combat, UI): if self.appear_then_click(REWARD_RECEIVE, offset=(20, 20), interval=3): confirm_timer.reset() continue - if self.appear(REWARD_RECEIVE_SP, offset=(20, 20), interval=3) \ - and REWARD_RECEIVE_SP.match_appear_on(self.device.image, threshold=15): + if self.match_template_color(REWARD_RECEIVE_SP, offset=(20, 20), interval=3, threshold=15): self.device.click(REWARD_RECEIVE_SP) confirm_timer.reset() continue @@ -83,6 +83,10 @@ class BattlePass(Combat, UI): if self.handle_battle_pass_popup(): confirm_timer.reset() continue + if self.config.SERVER == 'cn': + if self.appear_then_click(POPUP_CONFIRM_WHITE_BATTLEPASS, offset=(20, 20), interval=3): + confirm_timer.reset() + continue if self.handle_popup_confirm('BATTLE_PASS'): # Lock new META ships confirm_timer.reset() diff --git a/module/handler/fast_forward.py b/module/handler/fast_forward.py index 17caa94c9..d22961d84 100644 --- a/module/handler/fast_forward.py +++ b/module/handler/fast_forward.py @@ -1,4 +1,5 @@ import os +import re from module.base.timer import Timer from module.base.utils import color_bar_percentage @@ -7,15 +8,15 @@ from module.handler.auto_search import AutoSearchHandler from module.logger import logger from module.ui.switch import Switch -fast_forward = Switch('Fast_Forward') -fast_forward.add_status('on', check_button=FAST_FORWARD_ON) -fast_forward.add_status('off', check_button=FAST_FORWARD_OFF) -fleet_lock = Switch('Fleet_Lock', offset=(5, 20)) -fleet_lock.add_status('on', check_button=FLEET_LOCKED) -fleet_lock.add_status('off', check_button=FLEET_UNLOCKED) -auto_search = Switch('Auto_Search', offset=(20, 20)) -auto_search.add_status('on', check_button=AUTO_SEARCH_ON) -auto_search.add_status('off', check_button=AUTO_SEARCH_OFF) +FAST_FORWARD = Switch('Fast_Forward') +FAST_FORWARD.add_state('on', check_button=FAST_FORWARD_ON) +FAST_FORWARD.add_state('off', check_button=FAST_FORWARD_OFF) +FLEET_LOCK = Switch('Fleet_Lock', offset=(5, 20)) +FLEET_LOCK.add_state('on', check_button=FLEET_LOCKED) +FLEET_LOCK.add_state('off', check_button=FLEET_UNLOCKED) +AUTO_SEARCH = Switch('Auto_Search', offset=(20, 20)) +AUTO_SEARCH.add_state('on', check_button=AUTO_SEARCH_ON) +AUTO_SEARCH.add_state('off', check_button=AUTO_SEARCH_OFF) def map_files(event): @@ -51,7 +52,15 @@ def to_map_input_name(name: str) -> str: campaign_7_2 -> 7-2 d3 -> D3 """ - name = name.upper() + # Remove whitespaces + name = re.sub('[ \t\n]', '', name).lower() + # B-1 -> B1 + res = re.match(r'([a-zA-Z])+[- ]+(\d+)', name) + if res: + name = f'{res.group(1)}{res.group(2)}' + # Change back to upper case for campaign removal + name = str(name).upper() + # campaign_7_2 -> 7-2 name = name.replace('CAMPAIGN_', '').replace('_', '-') return name @@ -64,7 +73,14 @@ def to_map_file_name(name: str) -> str: campaign_7_2 -> campaign_7_2 D3 -> d3 """ - name = name.lower() + name = str(name).lower() + # Remove whitespaces + name = re.sub('[ \t\n]', '', name).lower() + # B-1 -> B1 + res = re.match(r'([a-zA-Z])+[- ]+(\d+)', name) + if res: + name = f'{res.group(1)}{res.group(2)}' + # 7-2 to campaign_7_2 if name and name[0].isdigit(): name = 'campaign_' + name.replace('-', '_') return name @@ -127,9 +143,9 @@ class FastForwardHandler(AutoSearchHandler): # Minor issue here # Using auto_search option because clear mode cannot be detected whether on SP # If user manually turn off auto search, alas can't enable it again - self.map_has_clear_mode = auto_search.appear(main=self) + self.map_has_clear_mode = AUTO_SEARCH.appear(main=self) else: - self.map_has_clear_mode = self.map_is_100_percent_clear and fast_forward.appear(main=self) + self.map_has_clear_mode = self.map_is_100_percent_clear and FAST_FORWARD.appear(main=self) # Override config if self.map_achieved_star_1: @@ -187,8 +203,8 @@ class FastForwardHandler(AutoSearchHandler): self.map_is_2x_book = False pass - status = 'on' if self.config.Campaign_UseClearMode else 'off' - changed = fast_forward.set(status=status, main=self) + state = 'on' if self.config.Campaign_UseClearMode else 'off' + changed = FAST_FORWARD.set(state, main=self) return changed def handle_map_fleet_lock(self, enable=None): @@ -201,14 +217,14 @@ class FastForwardHandler(AutoSearchHandler): """ # 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): + if not FLEET_LOCK.appear(main=self): logger.info('No fleet lock option.') return False if enable is None: enable = self.config.Campaign_UseFleetLock - status = 'on' if enable else 'off' - changed = fleet_lock.set(status=status, main=self) + state = 'on' if enable else 'off' + changed = FLEET_LOCK.set(state, main=self) return changed @@ -223,13 +239,13 @@ class FastForwardHandler(AutoSearchHandler): # if not self.map_is_clear_mode: # return False - if not auto_search.appear(main=self): + if not AUTO_SEARCH.appear(main=self): logger.info('No auto search option.') self.map_is_auto_search = False return False - status = 'on' if self.map_is_auto_search else 'off' - changed = auto_search.set(status=status, main=self) + state = 'on' if self.map_is_auto_search else 'off' + changed = AUTO_SEARCH.set(state, main=self) return changed @@ -476,8 +492,8 @@ class FastForwardHandler(AutoSearchHandler): book_check = BOOK_CHECK_AUTO book_box = BOOK_BOX_AUTO - status = 'on' if self.map_is_2x_book else 'off' - if self._set_2x_book_status(status, book_check, book_box): + state = 'on' if self.map_is_2x_book else 'off' + if self._set_2x_book_status(state, book_check, book_box): self.emotion.map_is_2x_book = self.map_is_2x_book else: self.map_is_2x_book = False diff --git a/module/handler/login.py b/module/handler/login.py index d170a5371..550783c05 100644 --- a/module/handler/login.py +++ b/module/handler/login.py @@ -53,7 +53,7 @@ class LoginHandler(UI): confirm_timer.reset() # Login - if self.appear(LOGIN_CHECK, offset=(30, 30), interval=5) and LOGIN_CHECK.match_appear_on(self.device.image): + if self.match_template_color(LOGIN_CHECK, offset=(30, 30), interval=5): self.device.click(LOGIN_CHECK) if not login_success: logger.info('Login success') diff --git a/module/handler/strategy.py b/module/handler/strategy.py index 7b78fd1e3..4cf9a4956 100644 --- a/module/handler/strategy.py +++ b/module/handler/strategy.py @@ -7,18 +7,18 @@ from module.template.assets import (TEMPLATE_FORMATION_1, TEMPLATE_FORMATION_2, from module.ui.switch import Switch # 2023.10.19, icons on one row increased from 2 to 3 -formation = Switch('Formation', offset=(100, 200)) -formation.add_status('line_ahead', check_button=FORMATION_1) -formation.add_status('double_line', check_button=FORMATION_2) -formation.add_status('diamond', check_button=FORMATION_3) +FORMATION = Switch('Formation', offset=(100, 200)) +FORMATION.add_state('line_ahead', check_button=FORMATION_1) +FORMATION.add_state('double_line', check_button=FORMATION_2) +FORMATION.add_state('diamond', check_button=FORMATION_3) -submarine_hunt = Switch('Submarine_hunt', offset=(200, 200)) -submarine_hunt.add_status('on', check_button=SUBMARINE_HUNT_ON) -submarine_hunt.add_status('off', check_button=SUBMARINE_HUNT_OFF) +SUBMARINE_HUNT = Switch('Submarine_hunt', offset=(200, 200)) +SUBMARINE_HUNT.add_state('on', check_button=SUBMARINE_HUNT_ON) +SUBMARINE_HUNT.add_state('off', check_button=SUBMARINE_HUNT_OFF) -submarine_view = Switch('Submarine_view', offset=(100, 200)) -submarine_view.add_status('on', check_button=SUBMARINE_VIEW_ON) -submarine_view.add_status('off', check_button=SUBMARINE_VIEW_OFF) +SUBMARINE_VIEW = Switch('Submarine_view', offset=(100, 200)) +SUBMARINE_VIEW.add_state('on', check_button=SUBMARINE_VIEW_ON) +SUBMARINE_VIEW.add_state('off', check_button=SUBMARINE_VIEW_OFF) MOB_MOVE_OFFSET = (120, 200) @@ -60,20 +60,20 @@ class StrategyHandler(InfoHandler): if not self.appear(STRATEGY_OPENED, offset=200): break - def strategy_set_execute(self, formation_index=None, sub_view=None, sub_hunt=None): + def strategy_set_execute(self, formation=None, sub_view=None, sub_hunt=None): """ Args: - formation_index (int): 1-3, or None for don't change + formation (str): 'line_ahead', 'double_line', 'diamond', or None for don't change sub_view (bool): sub_hunt (bool): Pages: in: STRATEGY_OPENED """ - logger.info(f'Strategy set: formation={formation_index}, submarine_view={sub_view}, submarine_hunt={sub_hunt}') + logger.info(f'Strategy set: formation={formation}, submarine_view={sub_view}, submarine_hunt={sub_hunt}') - if formation_index is not None: - formation.set(str(formation_index), main=self) + if formation is not None: + FORMATION.set(formation, main=self) # Disable this until the icon bug of submarine zone is fixed # And don't enable MAP_HAS_DYNAMIC_RED_BORDER when using submarine @@ -81,13 +81,13 @@ class StrategyHandler(InfoHandler): # Don't know when but the game bug was fixed, remove the use of SwitchWithHandler if sub_view is not None: - if submarine_view.appear(main=self): - submarine_view.set('on' if sub_view else 'off', main=self) + if SUBMARINE_VIEW.appear(main=self): + SUBMARINE_VIEW.set('on' if sub_view else 'off', main=self) else: logger.warning('Setting up submarine_view but no icon appears') if sub_hunt is not None: - if submarine_hunt.appear(main=self): - submarine_hunt.set('on' if sub_hunt else 'off', main=self) + if SUBMARINE_HUNT.appear(main=self): + SUBMARINE_HUNT.set('on' if sub_hunt else 'off', main=self) else: logger.warning('Setting up submarine_hunt but no icon appears') @@ -110,7 +110,7 @@ class StrategyHandler(InfoHandler): self.strategy_open() self.strategy_set_execute( - formation_index=expected_formation, + formation=expected_formation, sub_view=False, sub_hunt=bool(self.config.Submarine_Fleet) and self.config.Submarine_Mode in ['hunt_only', 'hunt_and_boss'] ) @@ -217,8 +217,7 @@ class StrategyHandler(InfoHandler): in: STRATEGY_OPENED out: STRATEGY_OPENED """ - if (self.appear(MOB_MOVE_ENTER, offset=MOB_MOVE_OFFSET) - and MOB_MOVE_ENTER.match_appear_on(self.device.image)): + if self.match_template_color(MOB_MOVE_ENTER, offset=MOB_MOVE_OFFSET): return True else: return False diff --git a/module/map/map_operation.py b/module/map/map_operation.py index 339fa6fc1..78d35c572 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -271,8 +271,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand return True if mode == 'normal': - if self.appear(MAP_MODE_SWITCH_NORMAL, offset=(20, 20)) \ - and MAP_MODE_SWITCH_NORMAL.match_appear_on(self.device.image): + if self.match_template_color(MAP_MODE_SWITCH_NORMAL, offset=(20, 20)): logger.attr('MAP_MODE_SWITCH', 'normal') return True elif self.appear(MAP_MODE_SWITCH_HARD, offset=(20, 20), interval=2): @@ -283,8 +282,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand else: return False elif mode == 'hard': - if self.appear(MAP_MODE_SWITCH_HARD, offset=(20, 20)) \ - and MAP_MODE_SWITCH_HARD.match_appear_on(self.device.image): + if self.match_template_color(MAP_MODE_SWITCH_HARD, offset=(20, 20)): logger.attr('MAP_MODE_SWITCH', 'hard') return True if self.appear(MAP_MODE_SWITCH_NORMAL, offset=(20, 20), interval=2): diff --git a/module/map_detection/grid_predictor.py b/module/map_detection/grid_predictor.py index 4e220f4cb..b58034a02 100644 --- a/module/map_detection/grid_predictor.py +++ b/module/map_detection/grid_predictor.py @@ -332,11 +332,11 @@ class GridPredictor: color = cv2.mean(crop(mask.image, area=np.rint(area).astype(int), copy=False)) return color[0] > 235 - def is_similar_to(self, grid, threshold=0.9): + def is_similar_to(self, grid, similarity=0.9): """ Args: grid (GridPredictor): Another Grid instance. - threshold (float): 0 to 1. + similarity (float): 0 to 1. Returns: bool: If current grid is similar to another. @@ -346,5 +346,5 @@ class GridPredictor: piece_1 = self._image_similar_piece piece_2 = grid._image_similar_full res = cv2.matchTemplate(piece_2, piece_1, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) - return similarity > threshold + _, sim, _, point = cv2.minMaxLoc(res) + return sim > similarity diff --git a/module/meowfficer/base.py b/module/meowfficer/base.py index 02f98159c..0c5fcdc1a 100644 --- a/module/meowfficer/base.py +++ b/module/meowfficer/base.py @@ -69,8 +69,7 @@ class MeowfficerBase(UI): self.device.screenshot() # End - if self.appear(MEOWFFICER_CHECK, offset=(20, 20)) \ - and MEOWFFICER_CHECK.match_appear_on(self.device.image): + if self.match_template_color(MEOWFFICER_CHECK, offset=(20, 20)): break else: if click_timer.reached(): diff --git a/module/meowfficer/buy.py b/module/meowfficer/buy.py index 213841480..c76abdba1 100644 --- a/module/meowfficer/buy.py +++ b/module/meowfficer/buy.py @@ -99,8 +99,7 @@ class MeowfficerBuy(MeowfficerBase): continue # End - if self.appear(MEOWFFICER_BUY_ENTER, offset=(20, 20)) \ - and MEOWFFICER_BUY_ENTER.match_appear_on(self.device.image): + if self.match_template_color(MEOWFFICER_BUY_ENTER, offset=(20, 20)): break def meow_buy(self) -> bool: diff --git a/module/meowfficer/collect.py b/module/meowfficer/collect.py index 673aa4a2c..5ce6a0373 100644 --- a/module/meowfficer/collect.py +++ b/module/meowfficer/collect.py @@ -15,12 +15,12 @@ MEOWFFICER_SHIFT_DETECT = Button( name='MEOWFFICER_SHIFT_DETECT') SWITCH_LOCK = Switch(name='Meowfficer_Lock', offset=(40, 40)) -SWITCH_LOCK.add_status( +SWITCH_LOCK.add_state( 'lock', check_button=MEOWFFICER_APPLY_UNLOCK, click_button=MEOWFFICER_APPLY_LOCK ) -SWITCH_LOCK.add_status( +SWITCH_LOCK.add_state( 'unlock', check_button=MEOWFFICER_APPLY_LOCK, click_button=MEOWFFICER_APPLY_UNLOCK @@ -75,8 +75,7 @@ class MeowfficerCollect(MeowfficerBase): Returns: bool """ - if self.appear(MEOWFFICER_GET_CHECK, offset=(40, 40)) and MEOWFFICER_GET_CHECK.match_appear_on( - self.device.image): + if self.match_template_color(MEOWFFICER_GET_CHECK, offset=(40, 40)): return True if self.appear(MEOWFFICER_TRAIN_START, offset=(20, 20)): @@ -176,7 +175,7 @@ class MeowfficerCollect(MeowfficerBase): lock (bool): """ # Apply designated lock status - SWITCH_LOCK.set(status='lock' if lock else 'unlock', main=self) + SWITCH_LOCK.set('lock' if lock else 'unlock', main=self) # Wait until info bar disappears self.ensure_no_info_bar(timeout=1) diff --git a/module/meta_reward/meta_reward.py b/module/meta_reward/meta_reward.py index 16bc07b8b..66512e98b 100644 --- a/module/meta_reward/meta_reward.py +++ b/module/meta_reward/meta_reward.py @@ -64,8 +64,7 @@ class BeaconReward(Combat, UI): else: self.device.screenshot() - if self.appear(REWARD_RECEIVE, offset=(20, 20), interval=3) and REWARD_RECEIVE.match_appear_on( - self.device.image): + if self.match_template_color(REWARD_RECEIVE, offset=(20, 20), interval=3): self.device.click(REWARD_RECEIVE) confirm_timer.reset() continue @@ -117,7 +116,7 @@ class DossierReward(Combat, UI): in: dossier meta page """ self.device.screenshot() - if self.appear(DOSSIER_REWARD_RECEIVE, offset=(-40, 10, -10, 40), threshold=0.7): + if self.appear(DOSSIER_REWARD_RECEIVE, offset=(-40, 10, -10, 40), similarity=0.7): logger.info('Found dossier reward red dot') return True else: @@ -166,8 +165,7 @@ class DossierReward(Combat, UI): else: self.device.screenshot() - if self.appear(DOSSIER_REWARD_RECEIVE, offset=(20, 20), interval=3) and DOSSIER_REWARD_RECEIVE.match_appear_on( - self.device.image): + if self.match_template_color(DOSSIER_REWARD_RECEIVE, offset=(20, 20), interval=3): self.device.click(DOSSIER_REWARD_RECEIVE) confirm_timer.reset() continue diff --git a/module/os/fleet.py b/module/os/fleet.py index aff0ecef0..77d63638a 100644 --- a/module/os/fleet.py +++ b/module/os/fleet.py @@ -369,7 +369,8 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh): # Arrive # Check colors, because screen goes black when something is unlocking. - if self.is_in_map() and IN_MAP.match_appear_on(self.device.image): + # A direct use of IN_MAP, basically `self.is_in_map() and IN_MAP.match_template_color()` + if self.match_template_color(IN_MAP, offset=(200, 5)): self.update_os() current = self.view.backend.homo_loca logger.attr('homo_loca', current) diff --git a/module/os/map_fleet_selector.py b/module/os/map_fleet_selector.py index a4e24bf74..0a50f5876 100644 --- a/module/os/map_fleet_selector.py +++ b/module/os/map_fleet_selector.py @@ -29,7 +29,7 @@ class FleetSelector: int: Index of current fleet, 1 to 4. return 0 if unrecognized. """ for index, button in enumerate([FLEET_1, FLEET_2, FLEET_3, FLEET_4]): - if self.main.appear(button, offset=(20, 20), threshold=0.75): + if self.main.appear(button, offset=(20, 20), similarity=0.75): return index + 1 logger.info('Unknown OpSi fleet') diff --git a/module/os_ash/meta.py b/module/os_ash/meta.py index b1312d4da..3920b86a6 100644 --- a/module/os_ash/meta.py +++ b/module/os_ash/meta.py @@ -231,12 +231,10 @@ class OpsiAshBeacon(Meta): else: self.device.screenshot() - if self.appear(META_INNER_PAGE_DAMAGE, offset=(20, 20)) \ - and META_INNER_PAGE_DAMAGE.match_appear_on(self.device.image): + if self.match_template_color(META_INNER_PAGE_DAMAGE, offset=(20, 20)): logger.info('Already in meta damage page') break - if self.appear(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20)) \ - and META_INNER_PAGE_NOT_DAMAGE.match_appear_on(self.device.image): + if self.match_template_color(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20)): logger.info('In meta details page, should switch to damage page') self.appear_then_click(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20), interval=2) continue diff --git a/module/os_handler/action_point.py b/module/os_handler/action_point.py index ad6486f45..3fcd91e11 100644 --- a/module/os_handler/action_point.py +++ b/module/os_handler/action_point.py @@ -108,8 +108,7 @@ class ActionPointHandler(UI, MapEventHandler): return self.appear(ACTION_POINT_USE, offset=(20, 20)) def is_current_ap_visible(self): - return self.appear(CURRENT_AP_CHECK, offset=(40, 5)) \ - and CURRENT_AP_CHECK.match_appear_on(self.device.image, threshold=15) + return self.match_template_color(CURRENT_AP_CHECK, offset=(40, 5), threshold=15) def action_point_use(self, skip_first_screenshot=True): prev = self._action_point_current diff --git a/module/os_handler/enemy_searching.py b/module/os_handler/enemy_searching.py index 383e61a31..2394a0cd1 100644 --- a/module/os_handler/enemy_searching.py +++ b/module/os_handler/enemy_searching.py @@ -10,7 +10,7 @@ class EnemySearchingHandler(EnemySearchingHandler_): def is_in_map(self): if IN_MAP.match_luma(self.device.image, offset=(200, 5)): return True - if self.appear(MAP_GOTO_GLOBE_FOG, offset=(5, 5)) and MAP_GOTO_GLOBE_FOG.match_appear_on(self.device.image): + if self.match_template_color(MAP_GOTO_GLOBE_FOG, offset=(5, 5)): return True return False diff --git a/module/os_handler/map_event.py b/module/os_handler/map_event.py index 653ed46a9..7a640a3be 100644 --- a/module/os_handler/map_event.py +++ b/module/os_handler/map_event.py @@ -1,12 +1,13 @@ from module.base.timer import Timer from module.combat.assets import * from module.exception import CampaignEnd -from module.handler.assets import * +from module.handler.assets import POPUP_CANCEL, POPUP_CONFIRM from module.logger import logger from module.os.assets import GLOBE_GOTO_MAP from module.os_handler.assets import * from module.os_handler.enemy_searching import EnemySearchingHandler from module.statistics.azurstats import DropImage +from module.ui.assets import BACK_ARROW from module.ui.switch import Switch @@ -19,8 +20,8 @@ class FleetLockSwitch(Switch): fleet_lock = FleetLockSwitch('Fleet_Lock', offset=(10, 120)) -fleet_lock.add_status('on', check_button=OS_FLEET_LOCKED) -fleet_lock.add_status('off', check_button=OS_FLEET_UNLOCKED) +fleet_lock.add_state('on', check_button=OS_FLEET_LOCKED) +fleet_lock.add_state('off', check_button=OS_FLEET_UNLOCKED) class MapEventHandler(EnemySearchingHandler): @@ -208,7 +209,6 @@ class MapEventHandler(EnemySearchingHandler): if drop: drop.handle_add(main=self, before=4) self.device.click(AUTO_SEARCH_REWARD) - self.clicked = True self.interval_reset([ AUTO_SEARCH_REWARD, AUTO_SEARCH_OS_MAP_OPTION_ON, @@ -225,6 +225,14 @@ class MapEventHandler(EnemySearchingHandler): # because of duplicated clicks and clicks to places outside the map confirm_timer.reset() continue + # Donno why but it just entered storage, exit it anyway + # Equivalent to is_in_storage, but can't inherit StorageHandler here + # STORAGE_CHECK is a duplicate name, this is the os_handler/STORAGE_CHECK, not handler/STORAGE_CHECK + if self.appear(STORAGE_CHECK, offset=(20, 20), interval=5): + logger.info(f'{STORAGE_CHECK} -> {BACK_ARROW}') + self.device.click(BACK_ARROW) + confirm_timer.reset() + continue # End if self.is_in_map(): @@ -244,14 +252,12 @@ class MapEventHandler(EnemySearchingHandler): Returns: bool: If clicked. """ - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)): if self.info_bar_count() >= 2: self.device.screenshot_interval_set() self.os_auto_search_quit(drop=drop) raise CampaignEnd - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120)) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120)): if self.info_bar_count() >= 2: self.device.screenshot_interval_set() self.os_auto_search_quit(drop=drop) @@ -268,20 +274,17 @@ class MapEventHandler(EnemySearchingHandler): if enable is None: pass elif enable: - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120), interval=3) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120), interval=3): self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF) self.interval_reset(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED) return True # Game client bugged sometimes, AUTO_SEARCH_OS_MAP_OPTION_OFF grayed out but still functional - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120), interval=3) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120), interval=3): self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED) self.interval_reset(AUTO_SEARCH_OS_MAP_OPTION_OFF) return True else: - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120), interval=3) \ - and AUTO_SEARCH_OS_MAP_OPTION_ON.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120), interval=3): self.device.click(AUTO_SEARCH_OS_MAP_OPTION_ON) return True @@ -303,7 +306,7 @@ class MapEventHandler(EnemySearchingHandler): if enable is None: enable = self.config.Campaign_UseFleetLock - status = 'on' if enable else 'off' - changed = fleet_lock.set(status=status, main=self) + state = 'on' if enable else 'off' + changed = fleet_lock.set(state, main=self) return changed diff --git a/module/os_handler/mission.py b/module/os_handler/mission.py index 51a0697f3..081ac2de4 100644 --- a/module/os_handler/mission.py +++ b/module/os_handler/mission.py @@ -81,15 +81,13 @@ class MissionHandler(GlobeOperation, ZoneManager): # End if self.is_in_os_mission() \ and not self.appear(MISSION_FINISH, offset=(20, 20)) \ - and not (self.appear(MISSION_CHECKOUT, offset=(20, 20)) - and MISSION_CHECKOUT.match_appear_on(self.device.image)): + and not self.match_template_color(MISSION_CHECKOUT, offset=(20, 20)): # No mission found, wait to confirm. Missions might not be loaded so fast. if confirm_timer.reached(): logger.info('No OS mission found.') break elif self.is_in_os_mission() \ - and (self.appear(MISSION_CHECKOUT, offset=(20, 20)) - and MISSION_CHECKOUT.match_appear_on(self.device.image)): + and self.match_template_color(MISSION_CHECKOUT, offset=(20, 20)): # Found one mission. logger.info('Found at least one OS missions.') break @@ -119,8 +117,7 @@ class MissionHandler(GlobeOperation, ZoneManager): logger.info('Monthly BOSS mission found, checking missions bellow it') checkout_offset = (-20, 100, 20, 150) - if not (self.appear(MISSION_CHECKOUT, offset=checkout_offset) - and MISSION_CHECKOUT.match_appear_on(self.device.image)): + if not self.match_template_color(MISSION_CHECKOUT, offset=checkout_offset): # If not having enough items to claim a mission, # there will still be MISSION_CHECKOUT, but button is transparent. # So here needs to use both template matching and color detection. diff --git a/module/os_handler/os_status.py b/module/os_handler/os_status.py index b97a135c2..c668881a8 100644 --- a/module/os_handler/os_status.py +++ b/module/os_handler/os_status.py @@ -17,7 +17,7 @@ from module.log_res.log_res import LogRes if server.server != 'jp': OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(239, 239, 239), threshold=160, name='OCR_SHOP_YELLOW_COINS') else: - OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(193, 193, 193), threshold=200, name='OCR_SHOP_YELLOW_COINS') + OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(201, 201, 201), threshold=200, name='OCR_SHOP_YELLOW_COINS') OCR_SHOP_PURPLE_COINS = Digit(SHOP_PURPLE_COINS, letter=(255, 255, 255), name='OCR_SHOP_PURPLE_COINS') OCR_OS_SHOP_PURPLE_COINS = Digit(OS_SHOP_PURPLE_COINS, letter=(255, 255, 255), name='OCR_OS_SHOP_PURPLE_COINS') diff --git a/module/os_handler/strategic.py b/module/os_handler/strategic.py index 8d639889a..2764fc8b7 100644 --- a/module/os_handler/strategic.py +++ b/module/os_handler/strategic.py @@ -25,8 +25,7 @@ class StrategicSearchHandler(MapEventHandler): continue if self.appear(AUTO_SEARCH_REWARD, offset=(50, 50)): continue - if self.appear(STRATEGIC_SEARCH_MAP_OPTION_OFF, offset=(20, 20), interval=2) \ - and STRATEGIC_SEARCH_MAP_OPTION_OFF.match_appear_on(self.device.image): + if self.match_template_color(STRATEGIC_SEARCH_MAP_OPTION_OFF, offset=(20, 20), interval=2): self.device.click(STRATEGIC_SEARCH_MAP_OPTION_OFF) self.device.sleep(1) ''' @@ -110,7 +109,7 @@ class StrategicSearchHandler(MapEventHandler): else: self.device.screenshot() - self.appear(STRATEGIC_SEARCH_DEVICE_CHECK, offset=(20, 200), threshold=0.7) + self.appear(STRATEGIC_SEARCH_DEVICE_CHECK, offset=(20, 200), similarity=0.7) STRATEGIC_SEARCH_DEVICE_STOP.load_offset(STRATEGIC_SEARCH_DEVICE_CHECK) STRATEGIC_SEARCH_DEVICE_CONTINUE.load_offset(STRATEGIC_SEARCH_DEVICE_CHECK) @@ -135,7 +134,7 @@ class StrategicSearchHandler(MapEventHandler): else: self.device.screenshot() - self.appear(STRATEGIC_SEARCH_SUBMIT_CHECK, offset=(20, 20), threshold=0.7) + self.appear(STRATEGIC_SEARCH_SUBMIT_CHECK, offset=(20, 20), similarity=0.7) STRATEGIC_SEARCH_SUBMIT_OFF.load_offset(STRATEGIC_SEARCH_SUBMIT_CHECK) STRATEGIC_SEARCH_SUBMIT_ON.load_offset(STRATEGIC_SEARCH_SUBMIT_CHECK) diff --git a/module/research/project.py b/module/research/project.py index 6f8c8c627..4b8377b1f 100644 --- a/module/research/project.py +++ b/module/research/project.py @@ -181,14 +181,14 @@ def parse_time(string): return timedelta(hours=result[0], minutes=result[1], seconds=result[2]) -def match_template(image, template, area, offset=30, threshold=0.85): +def match_template(image, template, area, offset=30, similarity=0.85): """ Args: image (np.ndarray): Screenshot template (np.ndarray): area (tuple): Crop area of image. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. Lower than this value will return float(0). + similarity (float): 0-1. Similarity. Lower than this value will return float(0). Returns: similarity (float): """ @@ -199,8 +199,9 @@ def match_template(image, template, area, offset=30, threshold=0.85): image = crop(image, offset + area, copy=False) res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) _, sim, _, point = cv2.minMaxLoc(res) - similarity = sim if sim >= threshold else 0.0 - return similarity + if sim < similarity: + sim = 0.0 + return sim def get_research_series_jp_old(image): @@ -275,7 +276,7 @@ def get_research_genre_jp(image): """ genre = '' for button in RESEARCH_DETAIL_GENRE: - if button.match(image, offset=(30, 20), threshold=0.9): + if button.match(image, offset=(30, 20), similarity=0.9): # DETAIL_GENRE_H_0.name.split("_")[2] == 'H' genre = button.name.split("_")[2] break @@ -309,7 +310,7 @@ def get_research_cost_jp(image): template=template, area=DETAIL_COST.area, offset=(10, 10), - threshold=0.8) + similarity=0.8) if not sim: continue for cost in costs: @@ -344,7 +345,7 @@ def get_research_ship_jp(image): template=load_image(template), area=DETAIL_BLUEPRINT.area, offset=(10, 10), - threshold=0.9) + similarity=0.9) if sim > similarity: similarity = sim ship = name diff --git a/module/research/research.py b/module/research/research.py index 4328c056a..62b35b042 100644 --- a/module/research/research.py +++ b/module/research/research.py @@ -50,7 +50,7 @@ class RewardResearch(ResearchSelector, ResearchQueue, StorageHandler): Returns: bool: If reset success. """ - if not self.appear(RESET_AVAILABLE): + if not self.appear(RESET_AVAILABLE, threshold=10): logger.info('Research reset unavailable') return False @@ -63,7 +63,7 @@ class RewardResearch(ResearchSelector, ResearchQueue, StorageHandler): else: self.device.screenshot() - if self.appear_then_click(RESET_AVAILABLE, interval=10): + if self.appear_then_click(RESET_AVAILABLE, interval=10, threshold=10): continue if self.handle_popup_confirm('RESEARCH_RESET'): executed = True diff --git a/module/retire/dock.py b/module/retire/dock.py index b85e79e9c..69ba363e9 100644 --- a/module/retire/dock.py +++ b/module/retire/dock.py @@ -14,12 +14,12 @@ from module.ui.ui import UI from module.equipment.equipment import Equipment DOCK_SORTING = Switch('Dork_sorting') -DOCK_SORTING.add_status('Ascending', check_button=SORT_ASC, click_button=SORTING_CLICK) -DOCK_SORTING.add_status('Descending', check_button=SORT_DESC, click_button=SORTING_CLICK) +DOCK_SORTING.add_state('Ascending', check_button=SORT_ASC, click_button=SORTING_CLICK) +DOCK_SORTING.add_state('Descending', check_button=SORT_DESC, click_button=SORTING_CLICK) DOCK_FAVOURITE = Switch('Favourite_filter') -DOCK_FAVOURITE.add_status('on', check_button=COMMON_SHIP_FILTER_ENABLE) -DOCK_FAVOURITE.add_status('off', check_button=COMMON_SHIP_FILTER_DISABLE) +DOCK_FAVOURITE.add_state('on', check_button=COMMON_SHIP_FILTER_ENABLE) +DOCK_FAVOURITE.add_state('off', check_button=COMMON_SHIP_FILTER_DISABLE) CARD_GRIDS = ButtonGrid( origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 204), grid_shape=(7, 2), name='CARD') diff --git a/module/retire/retirement.py b/module/retire/retirement.py index 054e7a0b7..a01fbd313 100644 --- a/module/retire/retirement.py +++ b/module/retire/retirement.py @@ -90,6 +90,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): executed = False for button in [SHIP_CONFIRM, SHIP_CONFIRM_2, EQUIP_CONFIRM, EQUIP_CONFIRM_2, GET_ITEMS_1, SR_SSR_CONFIRM]: self.interval_clear(button) + self.popup_interval_clear() timeout = Timer(10, count=10).start() while 1: if skip_first_screenshot: @@ -111,37 +112,41 @@ class Retirement(Enhancement, QuickRetireSettingHandler): timeout.reset() # Click - if self.appear(SHIP_CONFIRM, offset=(30, 30), interval=2): - if SHIP_CONFIRM.match_appear_on(self.device.image): - self.device.click(SHIP_CONFIRM) + # Ship confirm, order by display hierarchy + if self._unable_to_enhance \ + or self.config.OldRetire_SR \ + or self.config.OldRetire_SSR \ + or self.config.Retirement_RetireMode == 'one_click_retire': + if self.handle_popup_confirm(name='RETIRE_SR_SSR', offset=(20, 50)): + self.interval_reset([SHIP_CONFIRM, SHIP_CONFIRM_2]) continue - else: - self.interval_clear(SHIP_CONFIRM) - if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=2): + if self.config.SERVER in ['cn', 'jp', 'tw'] and \ + self.appear_then_click(SR_SSR_CONFIRM, offset=(20, 50), interval=2): + self.interval_reset([SHIP_CONFIRM, SHIP_CONFIRM_2]) + continue + if self.match_template_color(SHIP_CONFIRM_2, offset=(30, 30), interval=2): if self.retire_keep_common_cv and not self._have_kept_cv: self.keep_one_common_cv() self.device.click(SHIP_CONFIRM_2) + # GET_ITEMS_1 is going to appear, avoid re-entering ship confirm self.interval_clear(GET_ITEMS_1) + self.interval_reset([SHIP_CONFIRM, SHIP_CONFIRM_2]) continue + if self.match_template_color(SHIP_CONFIRM, offset=(30, 30), interval=2): + self.device.click(SHIP_CONFIRM) + continue + # Equip confirm if self.appear_then_click(EQUIP_CONFIRM, offset=(30, 30), interval=2): executed = True continue if self.appear_then_click(EQUIP_CONFIRM_2, offset=(30, 30), interval=2): self.interval_clear(GET_ITEMS_1) continue + # Get items if self.appear(GET_ITEMS_1, offset=(30, 30), interval=2): self.device.click(GET_ITEMS_1_RETIREMENT_SAVE) self.interval_reset(SHIP_CONFIRM) continue - if self._unable_to_enhance \ - or self.config.OldRetire_SR \ - or self.config.OldRetire_SSR \ - or self.config.Retirement_RetireMode == 'one_click_retire': - if self.handle_popup_confirm(name='RETIRE_SR_SSR', offset=(20, 50)): - continue - if self.config.SERVER in ['cn', 'jp', 'tw'] and \ - self.appear_then_click(SR_SSR_CONFIRM, offset=(20, 50), interval=2): - continue def retirement_appear(self): return self.appear(RETIRE_APPEAR_1, offset=30) \ @@ -260,7 +265,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): if selected == 0: break self.device.screenshot() - if not (self.appear(SHIP_CONFIRM, offset=(30, 30)) and SHIP_CONFIRM.match_appear_on(self.device.image)): + if not self.match_template_color(SHIP_CONFIRM, offset=(30, 30)): logger.warning('No ship selected, retrying') continue @@ -474,6 +479,8 @@ class Retirement(Enhancement, QuickRetireSettingHandler): """ count = 0 RETIRE_COIN.load_color(self.device.image) + RETIRE_COIN._match_init = True + self.interval_clear(SHIP_CONFIRM_2) while 1: if skip_first_screenshot: @@ -482,13 +489,13 @@ class Retirement(Enhancement, QuickRetireSettingHandler): self.device.screenshot() # End - if not self.appear(RETIRE_COIN, threshold=0.97): + if not RETIRE_COIN.match(self.device.image, offset=(20, 20), similarity=0.97): return True if count > 3: logger.warning('_retire_select_one failed after 3 trial') return False - if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=3): + if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=2): self.device.click(button) count += 1 continue @@ -524,26 +531,58 @@ class Retirement(Enhancement, QuickRetireSettingHandler): return None def retirement_get_common_rarity_cv(self, skip_first_screenshot=False): - button = self.retirement_get_common_rarity_cv_in_page() - if button is not None: - return button + """ + Args: + skip_first_screenshot: - for _ in range(7): - if not RETIRE_CONFIRM_SCROLL.appear(main=self): - logger.info('Scroll bar disappeared, stop') - break - RETIRE_CONFIRM_SCROLL.next_page(main=self) + Returns: + Button: Button to click to remove ship from retire list + """ + swipe_count = 0 + disappear_confirm = Timer(2, count=6) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # Try to get CV button = self.retirement_get_common_rarity_cv_in_page() if button is not None: return button + + # Wait scroll bar + if RETIRE_CONFIRM_SCROLL.appear(main=self): + disappear_confirm.clear() + else: + disappear_confirm.start() + if disappear_confirm.reached(): + logger.warning('Scroll bar disappeared, stop') + break + else: + continue + if RETIRE_CONFIRM_SCROLL.at_bottom(main=self): logger.info('Scroll bar reached end, stop') break + # Swipe next page + if swipe_count >= 7: + logger.info('Reached maximum swipes to find common CV') + break + RETIRE_CONFIRM_SCROLL.next_page(main=self) + swipe_count += 1 + return button def keep_one_common_cv(self): + """ + Returns: + + """ + logger.info('Keep one common CV') button = self.retirement_get_common_rarity_cv() if button is not None: self._retire_select_one(button) self._have_kept_cv = True + logger.info('Keep one common CV end') diff --git a/module/reward/reward.py b/module/reward/reward.py index 21f660778..4d790ba4b 100644 --- a/module/reward/reward.py +++ b/module/reward/reward.py @@ -103,8 +103,7 @@ class Reward(UI): for button in [MISSION_MULTI, MISSION_SINGLE]: if not click_timer.reached(): continue - if self.appear(button, offset=(20, 200), interval=interval) \ - and button.match_appear_on(self.device.image): + if self.match_template_color(button, offset=(20, 200), interval=interval): self.device.click(button) exit_timer.reset() click_timer.reset() diff --git a/module/statistics/azurstats.py b/module/statistics/azurstats.py index 45fbfa200..c815479dd 100644 --- a/module/statistics/azurstats.py +++ b/module/statistics/azurstats.py @@ -67,7 +67,10 @@ class DropImage: return len(self.images) def __bool__(self): - return self.save or self.upload + # Uncomment these if stats service re-run in the future + # return self.save or self.upload + + return self.save def __enter__(self): return self @@ -217,6 +220,8 @@ class AzurStats: save_thread = threading.Thread( target=self._save, args=(image, genre, filename)) save_thread.start() + + # Uncomment these if stats service re-run in the future # if upload: # upload_thread = threading.Thread( # target=self._upload, args=(image, genre, filename)) diff --git a/module/storage/ui.py b/module/storage/ui.py index 06e53df1f..9ba518954 100644 --- a/module/storage/ui.py +++ b/module/storage/ui.py @@ -40,8 +40,7 @@ class StorageUI(UI): Returns: bool, if in MATERIAL_CHECK, appear and match_appear_on """ - return self.appear(MATERIAL_CHECK, offset=(20, 20), interval=interval) \ - and MATERIAL_CHECK.match_appear_on(self.device.image) + return self.match_template_color(MATERIAL_CHECK, offset=(20, 20), interval=interval) def _storage_enter_material(self, skip_first_screenshot=True): """ diff --git a/module/tactical/tactical_class.py b/module/tactical/tactical_class.py index 052e5d27f..0cd10ade2 100644 --- a/module/tactical/tactical_class.py +++ b/module/tactical/tactical_class.py @@ -243,18 +243,23 @@ class RewardTacticalClass(Dock): book (Book): skip_first_screenshot (bool): """ + logger.info(f'Book select {book}') + interval = Timer(2, count=6) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() - if not book.check_selected(self.device.image): - self.device.click(book.button) - self.device.sleep((0.3, 0.5)) - else: + # End + if book.check_selected(self.device.image): break + if interval.reached(): + self.device.click(book.button) + interval.reset() + continue + def _tactical_books_filter_exp(self): """ Complex filter to remove specific grade diff --git a/module/ui/switch.py b/module/ui/switch.py index a72b9f366..c222bee17 100644 --- a/module/ui/switch.py +++ b/module/ui/switch.py @@ -1,5 +1,4 @@ from module.base.base import ModuleBase -from module.base.button import Button from module.base.timer import Timer from module.exception import ScriptError from module.logger import logger @@ -7,16 +6,15 @@ from module.logger import logger class Switch: """ - A wrapper to handle switches in game. - Set switch status with reties. + A wrapper to handle switches in game, switch among states with retries. Examples: # Definitions submarine_hunt = Switch('Submarine_hunt', offset=120) - submarine_hunt.add_status('on', check_button=SUBMARINE_HUNT_ON) - submarine_hunt.add_status('off', check_button=SUBMARINE_HUNT_OFF) + submarine_hunt.add_state('on', check_button=SUBMARINE_HUNT_ON) + submarine_hunt.add_state('off', check_button=SUBMARINE_HUNT_OFF) - # Change status to ON + # Change state to ON submarine_view.set('on', main=self) """ @@ -28,23 +26,24 @@ class Switch: For example: | [Daily] | Urgent | -> click -> | Daily | [Urgent] | False if this is a switch, click the switch itself, and it changed in the same position. For example: | [ON] | -> click -> | [OFF] | - offset (bool, int, tuple): Global offset in current switch """ self.name = name - self.is_choice = is_selector + self.is_selector = is_selector self.offset = offset - self.status_list = [] + self.state_list = [] - def add_status(self, status, check_button, click_button=None, offset=0): + def add_state(self, state, check_button, click_button=None, offset=0): """ Args: - status (str): + state (str): State name but cannot use 'unknown' as state name check_button (Button): click_button (Button): offset (bool, int, tuple): """ - self.status_list.append({ - 'status': status, + if state == 'unknown': + raise ScriptError(f'Cannot use "unknown" as state name') + self.state_list.append({ + 'state': state, 'check_button': check_button, 'click_button': click_button if click_button is not None else check_button, 'offset': offset if offset else self.offset @@ -58,7 +57,7 @@ class Switch: Returns: bool """ - for data in self.status_list: + for data in self.state_list: if main.appear(data['check_button'], offset=data['offset']): return True @@ -70,31 +69,39 @@ class Switch: main (ModuleBase): Returns: - str: Status name or 'unknown'. + str: state name or 'unknown'. """ - for data in self.status_list: + for data in self.state_list: if main.appear(data['check_button'], offset=data['offset']): - return data['status'] + return data['state'] return 'unknown' - def get_data(self, status): + def click(self, state, main): """ Args: - status (str): + state (str): + main (ModuleBase): + """ + button = self.get_data(state)['click_button'] + main.device.click(button) + + def get_data(self, state): + """ + Args: + state (str): Returns: - dict: Dictionary in add_status + dict: Dictionary in add_state Raises: - ScriptError: If status invalid + ScriptError: If state invalid """ - for row in self.status_list: - if row['status'] == status: + for row in self.state_list: + if row['state'] == state: return row - logger.warning(f'Switch {self.name} received an invalid status {status}') - raise ScriptError(f'Switch {self.name} received an invalid status {status}') + raise ScriptError(f'Switch {self.name} received an invalid state: {state}') def handle_additional(self, main): """ @@ -106,21 +113,22 @@ class Switch: """ return False - def set(self, status, main, skip_first_screenshot=True): + def set(self, state, main, skip_first_screenshot=True): """ Args: - status (str): + state: main (ModuleBase): skip_first_screenshot (bool): Returns: - bool: + bool: If clicked """ - self.get_data(status) + logger.info(f'{self.name} set to {state}') + self.get_data(state) - counter = 0 changed = False - warning_show_timer = Timer(5, count=10).start() + has_unknown = False + unknown_timer = Timer(5, count=10).start() click_timer = Timer(1, count=3) while 1: if skip_first_screenshot: @@ -132,31 +140,81 @@ class Switch: current = self.get(main=main) logger.attr(self.name, current) + # End + if current == state: + return changed + # Handle additional popups if self.handle_additional(main=main): continue - # End - if current == status: - return changed - # Warning if current == 'unknown': - if warning_show_timer.reached(): - logger.warning(f'Unknown {self.name} switch') - warning_show_timer.reset() - if counter >= 1: - logger.warning(f'{self.name} switch {status} asset has evaluated to unknown too many times, ' - f'asset should be re-verified') - return False - counter += 1 - continue + if unknown_timer.reached(): + logger.warning(f'Switch {self.name} has states evaluated to unknown, ' + f'asset should be re-verified') + has_unknown = True + unknown_timer.reset() + # If unknown_timer never reached, don't click when having an unknown state, + # the unknown state is probably the switching animation. + # If unknown_timer reached once, click target state ignoring whether state is unknown or not, + # the unknown state is probably a new state not yet added. + # By ignoring new states, Switch.set() can still switch among known states. + if not has_unknown: + continue + else: + # Known state, reset timer + unknown_timer.reset() # Click if click_timer.reached(): - click_status = status if self.is_choice else current - main.device.click(self.get_data(click_status)['click_button']) - click_timer.reset() + if self.is_selector: + # Click target state to switch + click_state = state + else: + # If this is a selector, click on current state to switch to another + # But 'unknown' is not clickable, if it is, click target state instead + # assuming all selector states share the same position. + if current == 'unknown': + click_state = state + else: + click_state = current + self.click(click_state, main=main) changed = True + click_timer.reset() + unknown_timer.reset() return changed + + def wait(self, main, skip_first_screenshot=True): + """ + Wait until any state activated + + Args: + main (ModuleBase): + skip_first_screenshot: + + Returns: + bool: If success + """ + timeout = Timer(2, count=6).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + main.device.screenshot() + + # Detect + current = self.get(main=main) + logger.attr(self.name, current) + + # End + if current != 'unknown': + return True + if timeout.reached(): + logger.warning(f'{self.name} wait activated timeout') + return False + + # Handle additional popups + if self.handle_additional(main=main): + continue diff --git a/module/ui/ui.py b/module/ui/ui.py index e58130ee8..a3470de6b 100644 --- a/module/ui/ui.py +++ b/module/ui/ui.py @@ -518,7 +518,7 @@ class UI(InfoHandler): return True # Dorm popup - if self.appear(DORM_INFO, offset=(30, 30), threshold=0.75, interval=3): + if self.appear(DORM_INFO, offset=(30, 30), similarity=0.75, interval=3): self.device.click(DORM_INFO) return True if self.appear_then_click(DORM_FEED_CANCEL, offset=(30, 30), interval=3): diff --git a/module/ui_white/assets.py b/module/ui_white/assets.py index c83714ccb..34b3f5f04 100644 --- a/module/ui_white/assets.py +++ b/module/ui_white/assets.py @@ -25,6 +25,7 @@ MAIN_TAB_SWITCH_WHITE = Button(area={'cn': (966, 548, 999, 582), 'en': (966, 548 MISSION_NOTICE_WHITE = Button(area={'cn': (923, 657, 947, 671), 'en': (923, 657, 947, 671), 'jp': (923, 657, 947, 671), 'tw': (923, 657, 947, 671)}, color={'cn': (227, 168, 159), 'en': (227, 168, 159), 'jp': (227, 168, 159), 'tw': (227, 168, 159)}, button={'cn': (923, 657, 947, 671), 'en': (923, 657, 947, 671), 'jp': (923, 657, 947, 671), 'tw': (923, 657, 947, 671)}, file={'cn': './assets/cn/ui_white/MISSION_NOTICE_WHITE.png', 'en': './assets/en/ui_white/MISSION_NOTICE_WHITE.png', 'jp': './assets/jp/ui_white/MISSION_NOTICE_WHITE.png', 'tw': './assets/tw/ui_white/MISSION_NOTICE_WHITE.png'}) POPUP_CANCEL_WHITE = Button(area={'cn': (487, 491, 531, 513), 'en': (471, 492, 547, 513), 'jp': (481, 490, 534, 516), 'tw': (487, 491, 531, 513)}, color={'cn': (214, 214, 214), 'en': (205, 206, 205), 'jp': (202, 203, 202), 'tw': (214, 214, 214)}, button={'cn': (487, 491, 531, 513), 'en': (471, 492, 547, 513), 'jp': (481, 490, 534, 516), 'tw': (487, 491, 531, 513)}, file={'cn': './assets/cn/ui_white/POPUP_CANCEL_WHITE.png', 'en': './assets/en/ui_white/POPUP_CANCEL_WHITE.png', 'jp': './assets/jp/ui_white/POPUP_CANCEL_WHITE.png', 'tw': './assets/cn/ui_white/POPUP_CANCEL_WHITE.png'}) POPUP_CONFIRM_WHITE = Button(area={'cn': (746, 494, 791, 515), 'en': (727, 495, 810, 515), 'jp': (743, 491, 796, 518), 'tw': (746, 494, 791, 515)}, color={'cn': (133, 216, 255), 'en': (107, 207, 255), 'jp': (109, 207, 255), 'tw': (133, 216, 255)}, button={'cn': (746, 494, 791, 515), 'en': (727, 495, 810, 515), 'jp': (743, 491, 796, 518), 'tw': (746, 494, 791, 515)}, file={'cn': './assets/cn/ui_white/POPUP_CONFIRM_WHITE.png', 'en': './assets/en/ui_white/POPUP_CONFIRM_WHITE.png', 'jp': './assets/jp/ui_white/POPUP_CONFIRM_WHITE.png', 'tw': './assets/cn/ui_white/POPUP_CONFIRM_WHITE.png'}) +POPUP_CONFIRM_WHITE_BATTLEPASS = Button(area={'cn': (744, 490, 795, 513), 'en': (744, 490, 795, 513), 'jp': (744, 490, 795, 513), 'tw': (744, 490, 795, 513)}, color={'cn': (119, 211, 255), 'en': (119, 211, 255), 'jp': (119, 211, 255), 'tw': (119, 211, 255)}, button={'cn': (744, 490, 795, 513), 'en': (744, 490, 795, 513), 'jp': (744, 490, 795, 513), 'tw': (744, 490, 795, 513)}, file={'cn': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png', 'en': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png', 'jp': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png', 'tw': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png'}) POPUP_SINGLE_WHITE = Button(area={'cn': (623, 493, 668, 515), 'en': (623, 493, 668, 515), 'jp': (623, 493, 668, 515), 'tw': (623, 493, 668, 515)}, color={'cn': (131, 215, 255), 'en': (131, 215, 255), 'jp': (131, 215, 255), 'tw': (131, 215, 255)}, button={'cn': (623, 493, 668, 515), 'en': (623, 493, 668, 515), 'jp': (623, 493, 668, 515), 'tw': (623, 493, 668, 515)}, file={'cn': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png', 'en': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png', 'jp': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png', 'tw': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png'}) REWARD_1_WHITE = Button(area={'cn': (437, 278, 496, 306), 'en': (411, 283, 523, 300), 'jp': (442, 279, 491, 305), 'tw': (441, 280, 490, 306)}, color={'cn': (255, 193, 97), 'en': (255, 212, 150), 'jp': (255, 195, 101), 'tw': (255, 199, 111)}, button={'cn': (437, 278, 496, 306), 'en': (411, 283, 523, 300), 'jp': (442, 279, 491, 305), 'tw': (441, 280, 490, 306)}, file={'cn': './assets/cn/ui_white/REWARD_1_WHITE.png', 'en': './assets/en/ui_white/REWARD_1_WHITE.png', 'jp': './assets/jp/ui_white/REWARD_1_WHITE.png', 'tw': './assets/tw/ui_white/REWARD_1_WHITE.png'}) REWARD_2_WHITE = Button(area={'cn': (436, 419, 497, 448), 'en': (411, 425, 523, 442), 'jp': (442, 421, 492, 446), 'tw': (439, 422, 490, 448)}, color={'cn': (255, 192, 94), 'en': (255, 212, 146), 'jp': (255, 196, 103), 'tw': (255, 198, 109)}, button={'cn': (436, 419, 497, 448), 'en': (411, 425, 523, 442), 'jp': (442, 421, 492, 446), 'tw': (439, 422, 490, 448)}, file={'cn': './assets/cn/ui_white/REWARD_2_WHITE.png', 'en': './assets/en/ui_white/REWARD_2_WHITE.png', 'jp': './assets/jp/ui_white/REWARD_2_WHITE.png', 'tw': './assets/tw/ui_white/REWARD_2_WHITE.png'})