diff --git a/assets/cn/battle_pass/REWARDS_CLAIM_ALL.png b/assets/cn/battle_pass/REWARDS_CLAIM_ALL.png new file mode 100644 index 000000000..62ab6eab8 Binary files /dev/null and b/assets/cn/battle_pass/REWARDS_CLAIM_ALL.png differ diff --git a/assets/share/battle_pass/CLOSE_CHOOSE_GIFT.png b/assets/share/battle_pass/CLOSE_CHOOSE_GIFT.png new file mode 100644 index 000000000..1d722f40e Binary files /dev/null and b/assets/share/battle_pass/CLOSE_CHOOSE_GIFT.png differ diff --git a/assets/share/battle_pass/EXP_CLAIM_ALL.png b/assets/share/battle_pass/EXP_CLAIM_ALL.png new file mode 100644 index 000000000..42485accd Binary files /dev/null and b/assets/share/battle_pass/EXP_CLAIM_ALL.png differ diff --git a/assets/share/battle_pass/MISSIONS_CHECK.png b/assets/share/battle_pass/MISSIONS_CHECK.png new file mode 100644 index 000000000..be67a9546 Binary files /dev/null and b/assets/share/battle_pass/MISSIONS_CHECK.png differ diff --git a/assets/share/battle_pass/MISSIONS_CLICK.png b/assets/share/battle_pass/MISSIONS_CLICK.png new file mode 100644 index 000000000..aa2acf316 Binary files /dev/null and b/assets/share/battle_pass/MISSIONS_CLICK.png differ diff --git a/assets/share/battle_pass/MISSIONS_LOADED.png b/assets/share/battle_pass/MISSIONS_LOADED.png new file mode 100644 index 000000000..827f1d5b5 Binary files /dev/null and b/assets/share/battle_pass/MISSIONS_LOADED.png differ diff --git a/assets/share/battle_pass/REWARDS_CHECK.png b/assets/share/battle_pass/REWARDS_CHECK.png new file mode 100644 index 000000000..184fe18a8 Binary files /dev/null and b/assets/share/battle_pass/REWARDS_CHECK.png differ diff --git a/assets/share/battle_pass/REWARDS_CLICK.png b/assets/share/battle_pass/REWARDS_CLICK.png new file mode 100644 index 000000000..6c635a972 Binary files /dev/null and b/assets/share/battle_pass/REWARDS_CLICK.png differ diff --git a/assets/share/battle_pass/REWARDS_LOADED.png b/assets/share/battle_pass/REWARDS_LOADED.png new file mode 100644 index 000000000..dbbf2f1e7 Binary files /dev/null and b/assets/share/battle_pass/REWARDS_LOADED.png differ diff --git a/config/template.json b/config/template.json index f6557a738..7811b8717 100644 --- a/config/template.json +++ b/config/template.json @@ -51,5 +51,13 @@ "Command": "DailyQuest", "ServerUpdate": "04:00" } + }, + "BattlePass": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "BattlePass", + "ServerUpdate": "04:00" + } } } \ No newline at end of file diff --git a/dev_tools/keyword_extract.py b/dev_tools/keyword_extract.py index 98321d20f..10a0dd701 100644 --- a/dev_tools/keyword_extract.py +++ b/dev_tools/keyword_extract.py @@ -168,6 +168,8 @@ class KeywordExtract: text_convert=dungeon_name) self.load_keywords(['传送', '追踪']) self.write_keywords(keyword_class='DungeonEntrance', output_file='./tasks/dungeon/keywords/dungeon_entrance.py') + self.load_keywords(['奖励', '任务']) + self.write_keywords(keyword_class='BattlePassTab', output_file='./tasks/battle_pass/keywords/tab.py') if __name__ == '__main__': diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 1dd469157..b4b93b751 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -235,5 +235,28 @@ "display": "hide" } } + }, + "BattlePass": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "BattlePass", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + } } } \ No newline at end of file diff --git a/module/config/argument/menu.json b/module/config/argument/menu.json index e507742e6..4be8fdcd5 100644 --- a/module/config/argument/menu.json +++ b/module/config/argument/menu.json @@ -12,7 +12,8 @@ "page": "setting", "tasks": [ "Dungeon", - "DailyQuest" + "DailyQuest", + "BattlePass" ] } } \ No newline at end of file diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index 3a251f974..df48b67ff 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -27,3 +27,5 @@ Daily: - Dungeon DailyQuest: - Scheduler + BattlePass: + - Scheduler diff --git a/module/config/config_manual.py b/module/config/config_manual.py index a4c273e44..695b688ad 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -1,5 +1,3 @@ -from pywebio.io_ctrl import Output - import module.config.server as server @@ -10,7 +8,7 @@ class ManualConfig: SCHEDULER_PRIORITY = """ Restart - > Dungeon > DailyQuest + > Dungeon > DailyQuest > BattlePass """ """ diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index fc4bfe50d..65f9764e1 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -25,6 +25,10 @@ "DailyQuest": { "name": "Daily Quest", "help": "" + }, + "BattlePass": { + "name": "Nameless Honor", + "help": "" } }, "Scheduler": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index e87aaf217..53e8ae4c1 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -25,6 +25,10 @@ "DailyQuest": { "name": "Task.DailyQuest.name", "help": "Task.DailyQuest.help" + }, + "BattlePass": { + "name": "ナナシの勲功", + "help": "" } }, "Scheduler": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 3eba4d86a..92c0655c2 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -25,6 +25,10 @@ "DailyQuest": { "name": "每日任务", "help": "" + }, + "BattlePass": { + "name": "无名勋礼", + "help": "" } }, "Scheduler": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index c07c6222f..896196f48 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -25,6 +25,10 @@ "DailyQuest": { "name": "每日任務", "help": "" + }, + "BattlePass": { + "name": "無名勳禮", + "help": "" } }, "Scheduler": { diff --git a/src.py b/src.py index 552812335..d4c3ff97d 100644 --- a/src.py +++ b/src.py @@ -30,6 +30,10 @@ class StarRailCopilot(AzurLaneAutoScript): from tasks.daily.daily_quest import DailyQuestUI DailyQuestUI(config=self.config, device=self.device).run() + def battle_pass(self): + from tasks.battle_pass.battle_pass import BattlePassUI + BattlePassUI(config=self.config, device=self.device).run() + if __name__ == '__main__': src = StarRailCopilot('src') diff --git a/tasks/battle_pass/assets/assets_battle_pass.py b/tasks/battle_pass/assets/assets_battle_pass.py new file mode 100644 index 000000000..36a94ee80 --- /dev/null +++ b/tasks/battle_pass/assets/assets_battle_pass.py @@ -0,0 +1,95 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +CLOSE_CHOOSE_GIFT = ButtonWrapper( + name='CLOSE_CHOOSE_GIFT', + share=Button( + file='./assets/share/battle_pass/CLOSE_CHOOSE_GIFT.png', + area=(387, 634, 412, 658), + search=(367, 614, 432, 678), + color=(104, 99, 86), + button=(387, 634, 412, 658), + ), +) +EXP_CLAIM_ALL = ButtonWrapper( + name='EXP_CLAIM_ALL', + share=Button( + file='./assets/share/battle_pass/EXP_CLAIM_ALL.png', + area=(1016, 664, 1036, 686), + search=(996, 644, 1056, 706), + color=(118, 98, 58), + button=(1016, 664, 1036, 686), + ), +) +MISSIONS_CHECK = ButtonWrapper( + name='MISSIONS_CHECK', + share=Button( + file='./assets/share/battle_pass/MISSIONS_CHECK.png', + area=(44, 210, 74, 230), + search=(24, 190, 94, 250), + color=(68, 69, 68), + button=(44, 210, 74, 230), + ), +) +MISSIONS_CLICK = ButtonWrapper( + name='MISSIONS_CLICK', + share=Button( + file='./assets/share/battle_pass/MISSIONS_CLICK.png', + area=(44, 211, 74, 230), + search=(24, 191, 94, 250), + color=(145, 143, 139), + button=(44, 211, 74, 230), + ), +) +MISSIONS_LOADED = ButtonWrapper( + name='MISSIONS_LOADED', + share=Button( + file='./assets/share/battle_pass/MISSIONS_LOADED.png', + area=(1163, 573, 1179, 589), + search=(1143, 553, 1199, 609), + color=(186, 183, 170), + button=(1163, 573, 1179, 589), + ), +) +REWARDS_CHECK = ButtonWrapper( + name='REWARDS_CHECK', + share=Button( + file='./assets/share/battle_pass/REWARDS_CHECK.png', + area=(39, 119, 78, 140), + search=(19, 99, 98, 160), + color=(131, 133, 133), + button=(39, 119, 78, 140), + ), +) +REWARDS_CLAIM_ALL = ButtonWrapper( + name='REWARDS_CLAIM_ALL', + cn=Button( + file='./assets/cn/battle_pass/REWARDS_CLAIM_ALL.png', + area=(827, 665, 907, 684), + search=(807, 645, 927, 704), + color=(172, 136, 69), + button=(827, 665, 907, 684), + ), +) +REWARDS_CLICK = ButtonWrapper( + name='REWARDS_CLICK', + share=Button( + file='./assets/share/battle_pass/REWARDS_CLICK.png', + area=(44, 118, 73, 132), + search=(24, 98, 93, 152), + color=(119, 119, 114), + button=(44, 118, 73, 132), + ), +) +REWARDS_LOADED = ButtonWrapper( + name='REWARDS_LOADED', + share=Button( + file='./assets/share/battle_pass/REWARDS_LOADED.png', + area=(173, 286, 248, 324), + search=(153, 266, 268, 344), + color=(96, 96, 94), + button=(173, 286, 248, 324), + ), +) diff --git a/tasks/battle_pass/battle_pass.py b/tasks/battle_pass/battle_pass.py new file mode 100644 index 000000000..ef9934a2a --- /dev/null +++ b/tasks/battle_pass/battle_pass.py @@ -0,0 +1,154 @@ +import numpy as np + +from module.base.timer import Timer +from module.base.utils import get_color +from module.logger.logger import logger +from module.ui.switch import Switch +from tasks.base.assets.assets_base_page import BATTLE_PASS_CHECK +from tasks.base.assets.assets_base_popup import GET_REWARD +from tasks.base.page import page_battle_pass +from tasks.base.ui import UI +from tasks.battle_pass.assets.assets_battle_pass import * +from tasks.battle_pass.keywords import KEYWORD_BATTLE_PASS_TAB + +SWITCH_BATTLE_PASS_TAB = Switch('BattlePassTab', is_selector=True) +SWITCH_BATTLE_PASS_TAB.add_state( + KEYWORD_BATTLE_PASS_TAB.Rewards, + check_button=REWARDS_CHECK, + click_button=REWARDS_CLICK +) +SWITCH_BATTLE_PASS_TAB.add_state( + KEYWORD_BATTLE_PASS_TAB.Missions, + check_button=MISSIONS_CHECK, + click_button=MISSIONS_CLICK +) + + +class BattlePassUI(UI): + def _battle_pass_wait_rewards_loaded(self, skip_first_screenshot=True): + timeout = Timer(2, count=4).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if timeout.reached(): + logger.warning('Wait rewards tab loaded timeout') + break + if self.appear(REWARDS_LOADED): + logger.info('Rewards tab loaded') + break + + def _battle_pass_wait_missions_loaded(self, skip_first_screenshot=True): + timeout = Timer(2, count=4).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if timeout.reached(): + logger.warning('Wait missions tab loaded timeout') + break + color = get_color(self.device.image, MISSIONS_LOADED.area) + if np.mean(color) > 128: + logger.info('Missions tab loaded') + break + + def battle_pass_goto(self, state: KEYWORD_BATTLE_PASS_TAB): + """ + Args: + state: + + Examples: + self = BattlePassUI('alas') + self.device.screenshot() + self.battle_pass_goto(KEYWORDS_DUNGEON_TAB.Missions) + self.battle_pass_goto(KEYWORDS_DUNGEON_TAB.Rewards) + """ + logger.hr('Battle pass tab goto', level=2) + self.ui_ensure(page_battle_pass) + if SWITCH_BATTLE_PASS_TAB.set(state, main=self): + logger.info(f'Tab goto {state}, wait until loaded') + if state == KEYWORD_BATTLE_PASS_TAB.Missions: + self._battle_pass_wait_missions_loaded() + if state == KEYWORD_BATTLE_PASS_TAB.Rewards: + self._battle_pass_wait_rewards_loaded() + + def handle_choose_gifts(self, interval=5): + """ + Popup when you have purchase Nameless Glory + + Args: + interval: + + Returns: + If handled + """ + if self.appear_then_click(CLOSE_CHOOSE_GIFT, interval=interval): + return True + + return False + + def _claim_exp(self, skip_first_screenshot=True): + self.battle_pass_goto(KEYWORD_BATTLE_PASS_TAB.Missions) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if not self.appear(EXP_CLAIM_ALL): + break + if self.appear_then_click(EXP_CLAIM_ALL): + logger.info("All EXP claimed") + continue + + def _claim_rewards(self, skip_first_screenshot=True): + self.battle_pass_goto(KEYWORD_BATTLE_PASS_TAB.Rewards) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(GET_REWARD) or self.appear(CLOSE_CHOOSE_GIFT): + break + if self.appear_then_click(REWARDS_CLAIM_ALL): + continue + + skip_first_screenshot = True + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(BATTLE_PASS_CHECK): + logger.info("Claiming rewards complete") + break + if self.handle_choose_gifts(): + logger.info("You have unclaimed gift to choose") + continue + if self.handle_reward(): + continue + + def claim_battle_pass_rewards(self): + """ + Examples: + self = BattlePassUI('alas') + self.device.screenshot() + self.claim_rewards() + """ + self._claim_exp() + self._claim_rewards() + return True + + def run(self): + for _ in range(5): + claimed = self.claim_battle_pass_rewards() + if claimed: + break + + self.config.task_delay(server_update=True) diff --git a/tasks/battle_pass/keywords/__init__.py b/tasks/battle_pass/keywords/__init__.py new file mode 100644 index 000000000..55124b13f --- /dev/null +++ b/tasks/battle_pass/keywords/__init__.py @@ -0,0 +1,2 @@ +import tasks.battle_pass.keywords.tab as KEYWORD_BATTLE_PASS_TAB +from tasks.battle_pass.keywords.classes import BattlePassTab diff --git a/tasks/battle_pass/keywords/classes.py b/tasks/battle_pass/keywords/classes.py new file mode 100644 index 000000000..5b45e2b57 --- /dev/null +++ b/tasks/battle_pass/keywords/classes.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass +from typing import ClassVar + +from module.ocr.keyword import Keyword + + +@dataclass(repr=False) +class BattlePassTab(Keyword): + instances: ClassVar = {} diff --git a/tasks/battle_pass/keywords/tab.py b/tasks/battle_pass/keywords/tab.py new file mode 100644 index 000000000..5051712ba --- /dev/null +++ b/tasks/battle_pass/keywords/tab.py @@ -0,0 +1,21 @@ +from .classes import BattlePassTab + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Rewards = BattlePassTab( + id=1, + name='Rewards', + cn='奖励', + cht='獎勵', + en='Rewards', + jp='報酬', +) +Missions = BattlePassTab( + id=2, + name='Missions', + cn='任务', + cht='任務', + en='Missions', + jp='クエスト', +)