diff --git a/assets/share/rogue/reward/OCR_REMAIN.png b/assets/share/rogue/reward/OCR_REMAIN.png new file mode 100644 index 000000000..8a7dc0f12 Binary files /dev/null and b/assets/share/rogue/reward/OCR_REMAIN.png differ diff --git a/assets/share/rogue/reward/REWARD_CLOSE.png b/assets/share/rogue/reward/REWARD_CLOSE.png new file mode 100644 index 000000000..00e5ade4a Binary files /dev/null and b/assets/share/rogue/reward/REWARD_CLOSE.png differ diff --git a/assets/share/rogue/reward/USE_IMMERSIFIER.BUTTON.png b/assets/share/rogue/reward/USE_IMMERSIFIER.BUTTON.png new file mode 100644 index 000000000..1b97500a8 Binary files /dev/null and b/assets/share/rogue/reward/USE_IMMERSIFIER.BUTTON.png differ diff --git a/assets/share/rogue/reward/USE_IMMERSIFIER.png b/assets/share/rogue/reward/USE_IMMERSIFIER.png new file mode 100644 index 000000000..0e66aff24 Binary files /dev/null and b/assets/share/rogue/reward/USE_IMMERSIFIER.png differ diff --git a/assets/share/rogue/reward/USE_STAMINA.BUTTON.png b/assets/share/rogue/reward/USE_STAMINA.BUTTON.png new file mode 100644 index 000000000..a84939bf7 Binary files /dev/null and b/assets/share/rogue/reward/USE_STAMINA.BUTTON.png differ diff --git a/assets/share/rogue/reward/USE_STAMINA.png b/assets/share/rogue/reward/USE_STAMINA.png new file mode 100644 index 000000000..cf0012812 Binary files /dev/null and b/assets/share/rogue/reward/USE_STAMINA.png differ diff --git a/config/template.json b/config/template.json index f3555c1b4..0f9d6d2f5 100644 --- a/config/template.json +++ b/config/template.json @@ -58,6 +58,7 @@ }, "DungeonStorage": { "TrailblazePower": {}, + "Immersifier": {}, "DungeonDouble": {}, "EchoOfWar": {}, "SimulatedUniverse": {} diff --git a/module/config/argument/args.json b/module/config/argument/args.json index d5606991c..c41fa1c18 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -390,6 +390,12 @@ "order": 1, "color": "#eb8efe" }, + "Immersifier": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredImmersifier" + }, "DungeonDouble": { "type": "stored", "value": {}, diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 818fe0204..eaa472af9 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -116,6 +116,8 @@ DungeonStorage: stored: StoredTrailblazePower order: 1 color: "#eb8efe" + Immersifier: + stored: StoredImmersifier DungeonDouble: stored: StoredDungeonDouble EchoOfWar: diff --git a/module/config/argument/stored.json b/module/config/argument/stored.json index 734347e84..036e66a41 100644 --- a/module/config/argument/stored.json +++ b/module/config/argument/stored.json @@ -101,6 +101,19 @@ "order": 8, "color": "#fc8f8b" }, + "Immersifier": { + "name": "Immersifier", + "path": "Dungeon.DungeonStorage.Immersifier", + "i18n": "DungeonStorage.Immersifier.name", + "stored": "StoredImmersifier", + "attrs": { + "time": "2020-01-01 00:00:00", + "total": 8, + "value": 0 + }, + "order": 0, + "color": "#777777" + }, "DungeonDouble": { "name": "DungeonDouble", "path": "Dungeon.DungeonStorage.DungeonDouble", diff --git a/module/config/config_generated.py b/module/config/config_generated.py index da728f6bb..a3bbfe3fc 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -57,6 +57,7 @@ class GeneratedConfig: # Group `DungeonStorage` DungeonStorage_TrailblazePower = {} + DungeonStorage_Immersifier = {} DungeonStorage_DungeonDouble = {} DungeonStorage_EchoOfWar = {} DungeonStorage_SimulatedUniverse = {} diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 80fddad62..2fa8b9397 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -401,6 +401,10 @@ "name": "Power", "help": "" }, + "Immersifier": { + "name": "Immersifier", + "help": "" + }, "DungeonDouble": { "name": "Dungeon Double", "help": "" diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index d170f2b00..803cabf46 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -401,6 +401,10 @@ "name": "Poder", "help": "" }, + "Immersifier": { + "name": "Inmersor", + "help": "" + }, "DungeonDouble": { "name": "Mazmorra x2", "help": "" diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index c8e27557a..926814926 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -401,6 +401,10 @@ "name": "DungeonStorage.TrailblazePower.name", "help": "DungeonStorage.TrailblazePower.help" }, + "Immersifier": { + "name": "DungeonStorage.Immersifier.name", + "help": "DungeonStorage.Immersifier.help" + }, "DungeonDouble": { "name": "DungeonStorage.DungeonDouble.name", "help": "DungeonStorage.DungeonDouble.help" diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 79585611c..072e45783 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -401,6 +401,10 @@ "name": "开拓力", "help": "" }, + "Immersifier": { + "name": "沉浸器", + "help": "" + }, "DungeonDouble": { "name": "副本双倍", "help": "" diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index a8cb2cee8..9f791bd62 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -401,6 +401,10 @@ "name": "開拓力", "help": "" }, + "Immersifier": { + "name": "沉浸器", + "help": "" + }, "DungeonDouble": { "name": "副本雙倍", "help": "" @@ -806,20 +810,20 @@ "combat": "偏好戰鬥", "occurrence": "偏好事件" }, - "ImmersionReward": { - "name": "领取浸器奖励", - "help": "注意:选择 \"使用沉浸器和开拓力领取\" 时,每日副本任务不再打本,所有开拓力将优先被用于领取浸器奖励,双倍活动时除外", - "do_not_claim": "不领取", - "immersifier": "仅使用沉浸器领取", - "immersifier_trailblaze_power": "使用沉浸器和开拓力领取" - }, - "StopCondition": { - "name": "停止条件", - "help": "注意:\"每周100精英怪掉落奖励达到上限\" 时,模拟宇宙任务将运行数小时", - "weekly_point_reward": "每周点数奖励达到上限", - "100_elite_boss": "每周100精英怪掉落奖励达到上限", - "non_stop": "不停止 (仅用于除錯)" - } + "ImmersionReward": { + "name": "領取浸器獎勵", + "help": "注意:選擇 \"使用沉浸器和開拓力領取\" 時,每日副本任務不再打本,所有開拓力將優先被用於領取浸器獎勵,雙倍活動時除外", + "do_not_claim": "不領取", + "immersifier": "只使用沉浸器領取", + "immersifier_trailblaze_power": "使用沉浸器和開拓力領取" + }, + "StopCondition": { + "name": "停止條件", + "help": "注意:\"每週100精英怪掉落獎勵達到上限\" 時,模擬宇宙任務將運行數小時", + "weekly_point_reward": "每週點數獎勵達到上限", + "100_elite_boss": "每週100精英怪掉落獎勵達到上限", + "non_stop": "不停止 (只用於除錯)" + } }, "RoguePath": { "_info": { diff --git a/module/config/stored/classes.py b/module/config/stored/classes.py index 3b8e49070..483cae4a1 100644 --- a/module/config/stored/classes.py +++ b/module/config/stored/classes.py @@ -177,6 +177,29 @@ class StoredDailyActivity(StoredCounter, StoredExpiredAt0400): class StoredTrailblazePower(StoredCounter): FIXED_TOTAL = 240 + def predict_current(self) -> int: + """ + Predict current stamina from records + """ + # Overflowed + value = self.value + if value >= self.FIXED_TOTAL: + return value + # Invalid time, record in the future + record = self.time + now = datetime.now() + if record >= now: + return value + # Calculate + # Recover 1 trailbaze power each 6 minutes + diff = (now - record).total_seconds() + value += int(diff // 360) + return value + + +class StoredImmersifier(StoredCounter): + FIXED_TOTAL = 8 + class StoredSimulatedUniverse(StoredCounter, StoredExpiredAtMonday0400): pass diff --git a/module/config/stored/stored_generated.py b/module/config/stored/stored_generated.py index bd30c6831..c91cc8e78 100644 --- a/module/config/stored/stored_generated.py +++ b/module/config/stored/stored_generated.py @@ -10,6 +10,7 @@ from module.config.stored.classes import ( StoredEchoOfWar, StoredExpiredAt0400, StoredExpiredAtMonday0400, + StoredImmersifier, StoredInt, StoredSimulatedUniverse, StoredTrailblazePower, @@ -21,6 +22,7 @@ from module.config.stored.classes import ( class StoredGenerated: TrailblazePower = StoredTrailblazePower("Dungeon.DungeonStorage.TrailblazePower") + Immersifier = StoredImmersifier("Dungeon.DungeonStorage.Immersifier") DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble") EchoOfWar = StoredEchoOfWar("Dungeon.DungeonStorage.EchoOfWar") SimulatedUniverse = StoredSimulatedUniverse("Dungeon.DungeonStorage.SimulatedUniverse") diff --git a/tasks/rogue/assets/assets_rogue_reward.py b/tasks/rogue/assets/assets_rogue_reward.py new file mode 100644 index 000000000..416550e94 --- /dev/null +++ b/tasks/rogue/assets/assets_rogue_reward.py @@ -0,0 +1,45 @@ +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 ``` + +OCR_REMAIN = ButtonWrapper( + name='OCR_REMAIN', + share=Button( + file='./assets/share/rogue/reward/OCR_REMAIN.png', + area=(675, 11, 1181, 64), + search=(655, 0, 1201, 84), + color=(75, 89, 125), + button=(675, 11, 1181, 64), + ), +) +REWARD_CLOSE = ButtonWrapper( + name='REWARD_CLOSE', + share=Button( + file='./assets/share/rogue/reward/REWARD_CLOSE.png', + area=(1043, 194, 1073, 224), + search=(1023, 174, 1093, 244), + color=(174, 175, 178), + button=(1043, 194, 1073, 224), + ), +) +USE_IMMERSIFIER = ButtonWrapper( + name='USE_IMMERSIFIER', + share=Button( + file='./assets/share/rogue/reward/USE_IMMERSIFIER.png', + area=(713, 509, 737, 523), + search=(693, 489, 757, 543), + color=(189, 189, 189), + button=(684, 499, 963, 535), + ), +) +USE_STAMINA = ButtonWrapper( + name='USE_STAMINA', + share=Button( + file='./assets/share/rogue/reward/USE_STAMINA.png', + area=(345, 509, 378, 524), + search=(325, 489, 398, 544), + color=(172, 172, 172), + button=(319, 499, 595, 535), + ), +) diff --git a/tasks/rogue/event/reward.py b/tasks/rogue/event/reward.py new file mode 100644 index 000000000..feeff26c2 --- /dev/null +++ b/tasks/rogue/event/reward.py @@ -0,0 +1,117 @@ +from module.base.timer import Timer +from module.logger import logger +from module.ocr.ocr import DigitCounter +from tasks.base.assets.assets_base_popup import GET_REWARD +from tasks.combat.interact import CombatInteract +from tasks.rogue.assets.assets_rogue_reward import OCR_REMAIN, REWARD_CLOSE, USE_IMMERSIFIER, USE_STAMINA +from tasks.rogue.bleesing.ui import RogueUI + + +class RogueReward(RogueUI, CombatInteract): + def _reward_update_stamina(self, skip_first_screenshot=True): + ocr = DigitCounter(OCR_REMAIN) + timeout = Timer(1, count=2).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + stamina = (0, 0, 0) + immersifier = (0, 0, 0) + + if timeout.reached(): + logger.warning('_reward_update_stamina() timeout') + break + + for row in ocr.detect_and_ocr(self.device.image): + if row.ocr_text.isdigit(): + continue + if row.ocr_text == '+': + continue + data = ocr.format_result(row.ocr_text) + if data[2] == self.config.stored.TrailblazePower.FIXED_TOTAL: + stamina = data + if data[2] == self.config.stored.Immersifier.FIXED_TOTAL: + immersifier = data + + if stamina[2] > 0 and immersifier[2] > 0: + break + + stamina = stamina[0] + immersifier = immersifier[0] + logger.attr('TrailblazePower', stamina) + logger.attr('Imersifier', immersifier) + with self.config.multi_set(): + self.config.stored.TrailblazePower.value = stamina + self.config.stored.Immersifier.value = immersifier + + def claim_domain_reward( + self, + use_trailblaze_power=False, + use_immersifier=True, + skip_first_screenshot=True + ): + """ + Pages: + in: page_main, DUNGEON_COMBAT_INTERACT, near immersifier + """ + logger.hr('Claim domain reward', level=2) + logger.info(f'use_trailblaze_power={use_trailblaze_power}, use_immersifier={use_immersifier}') + if not use_trailblaze_power and not use_immersifier: + return + + confirm = Timer(0.6, count=2).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.is_in_main(): + if confirm.reached(): + break + else: + confirm.reset() + + if self.handle_combat_interact(): + self.interval_clear(USE_STAMINA) + confirm.reset() + continue + if self.handle_reward(): + self.interval_clear(USE_STAMINA) + confirm.reset() + continue + if self.appear(REWARD_CLOSE, interval=2): + self._reward_update_stamina() + if use_immersifier and self.config.stored.Immersifier.value > 0: + self.device.click(USE_IMMERSIFIER) + self.interval_reset(USE_STAMINA) + self.interval_clear(GET_REWARD) + confirm.reset() + continue + elif use_trailblaze_power and self.config.stored.TrailblazePower.value >= 40: + self.device.click(USE_STAMINA) + self.interval_reset(USE_STAMINA) + self.interval_clear(GET_REWARD) + confirm.reset() + continue + else: + logger.info('Cannot claim more rewards') + self.device.click(REWARD_CLOSE) + self.interval_reset(USE_STAMINA) + confirm.reset() + continue + + def can_claim_domain_reward( + self, + use_trailblaze_power=False, + use_immersifier=True + ): + if not use_trailblaze_power and not use_immersifier: + return False + if use_immersifier and self.config.stored.Immersifier.value >= 0: + return True + if use_trailblaze_power and self.config.stored.TrailblazePower.predict_current() >= 40: + return True + return False diff --git a/tasks/rogue/route/base.py b/tasks/rogue/route/base.py index 7c35a12ed..eb69a3b52 100644 --- a/tasks/rogue/route/base.py +++ b/tasks/rogue/route/base.py @@ -11,10 +11,11 @@ from tasks.rogue.bleesing.blessing import RogueBlessingSelector from tasks.rogue.bleesing.bonus import RogueBonusSelector from tasks.rogue.bleesing.curio import RogueCurioSelector from tasks.rogue.event.event import RogueEvent +from tasks.rogue.event.reward import RogueReward from tasks.rogue.route.exit import RogueExit -class RouteBase(RouteBase_, RogueExit, RogueEvent): +class RouteBase(RouteBase_, RogueExit, RogueEvent, RogueReward): registered_domain_exit = None def combat_expected_end(self): @@ -174,14 +175,16 @@ class RouteBase(RouteBase_, RogueExit, RogueEvent): Get reward of the DomainElite and DomainBoss """ logger.hr('Clear reward', level=1) + use_trailblaze_power = 'trailblaze' in self.config.RogueWorld_ImmersionReward + use_immersifier = 'immersifier' in self.config.RogueWorld_ImmersionReward - # TODO: Skip if user don't want rewards or stamina exhausted - return [] - - result = self.goto(*waypoints) - - # TODO: Get reward - pass + if self.can_claim_domain_reward(use_trailblaze_power=use_trailblaze_power, use_immersifier=use_immersifier): + logger.info('Can claim domain reward') + result = self.goto(*waypoints) + self.claim_domain_reward(use_trailblaze_power=use_trailblaze_power, use_immersifier=use_immersifier) + else: + logger.info('Cannot claim more rewards') + result = [] return result