diff --git a/assets/cn/retire/DOCK_CHECK.png b/assets/cn/retire/DOCK_CHECK.png new file mode 100644 index 000000000..2fe7b78e2 Binary files /dev/null and b/assets/cn/retire/DOCK_CHECK.png differ diff --git a/assets/cn/retire/DOCK_FILTER.png b/assets/cn/retire/DOCK_FILTER.png new file mode 100644 index 000000000..05deb3fab Binary files /dev/null and b/assets/cn/retire/DOCK_FILTER.png differ diff --git a/assets/cn/retire/DOCK_FILTER_CONFIRM.png b/assets/cn/retire/DOCK_FILTER_CONFIRM.png new file mode 100644 index 000000000..92157c4e9 Binary files /dev/null and b/assets/cn/retire/DOCK_FILTER_CONFIRM.png differ diff --git a/assets/cn/retire/ENHANCE_CONFIRM.png b/assets/cn/retire/ENHANCE_CONFIRM.png new file mode 100644 index 000000000..7c8eefcfd Binary files /dev/null and b/assets/cn/retire/ENHANCE_CONFIRM.png differ diff --git a/assets/cn/retire/ENHANCE_FILLED.png b/assets/cn/retire/ENHANCE_FILLED.png new file mode 100644 index 000000000..4aceeb415 Binary files /dev/null and b/assets/cn/retire/ENHANCE_FILLED.png differ diff --git a/assets/cn/retire/ENHANCE_RECOMMEND.png b/assets/cn/retire/ENHANCE_RECOMMEND.png new file mode 100644 index 000000000..0d46a3947 Binary files /dev/null and b/assets/cn/retire/ENHANCE_RECOMMEND.png differ diff --git a/assets/cn/retire/ENHANCE_RELOAD.png b/assets/cn/retire/ENHANCE_RELOAD.png new file mode 100644 index 000000000..14b59b771 Binary files /dev/null and b/assets/cn/retire/ENHANCE_RELOAD.png differ diff --git a/assets/cn/retire/FILTER_INDEX_ALL_OFF.png b/assets/cn/retire/FILTER_INDEX_ALL_OFF.png new file mode 100644 index 000000000..7694d83ad Binary files /dev/null and b/assets/cn/retire/FILTER_INDEX_ALL_OFF.png differ diff --git a/assets/cn/retire/FILTER_INDEX_ALL_ON.png b/assets/cn/retire/FILTER_INDEX_ALL_ON.png new file mode 100644 index 000000000..0121d0106 Binary files /dev/null and b/assets/cn/retire/FILTER_INDEX_ALL_ON.png differ diff --git a/assets/cn/retire/FILTER_INDEX_ENHANCEMENT_OFF.png b/assets/cn/retire/FILTER_INDEX_ENHANCEMENT_OFF.png new file mode 100644 index 000000000..01a89e865 Binary files /dev/null and b/assets/cn/retire/FILTER_INDEX_ENHANCEMENT_OFF.png differ diff --git a/assets/cn/retire/FILTER_INDEX_ENHANCEMENT_ON.png b/assets/cn/retire/FILTER_INDEX_ENHANCEMENT_ON.png new file mode 100644 index 000000000..41ff65ee1 Binary files /dev/null and b/assets/cn/retire/FILTER_INDEX_ENHANCEMENT_ON.png differ diff --git a/assets/cn/retire/SHIP_DETAIL_CHECK.png b/assets/cn/retire/SHIP_DETAIL_CHECK.png new file mode 100644 index 000000000..dab0733c9 Binary files /dev/null and b/assets/cn/retire/SHIP_DETAIL_CHECK.png differ diff --git a/campaign/event_20200521_en/a1.py b/campaign/event_20200521_en/a1.py index ed452dac4..3523049b5 100644 --- a/campaign/event_20200521_en/a1.py +++ b/campaign/event_20200521_en/a1.py @@ -13,6 +13,7 @@ MAP.map_data = ''' -- ME -- -- ++ ME -- ME -- SP -- -- ME ++ -- ME -- MB ''' +MAP.camera_data = ['D1', 'D3', 'F1', 'F3'] class Config: POOR_MAP_DATA = True @@ -25,6 +26,12 @@ class Config: TRUST_EDGE_LINES = True + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 40 + EDGE_LINES_HOUGHLINES_THRESHOLD = 40 + COINCIDENT_POINT_ENCOURAGE_DISTANCE = 1.5 + MID_DIFF_RANGE_H = (140 - 3, 140 + 3) + MID_DIFF_RANGE_V = (143 - 3, 143 + 3) + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { 'height': (80, 255 - 40), 'width': (0.9, 10), @@ -42,3 +49,8 @@ class Config: class Campaign(CampaignBase): MAP = MAP + def handle_boss_appear_refocus(self): + if self.battle_count == 4: + self.map_swipe((-3, -2)) + + return super().handle_boss_appear_refocus() diff --git a/campaign/event_20200521_en/b2.py b/campaign/event_20200521_en/b2.py index f8a088fbe..9d3b321dc 100644 --- a/campaign/event_20200521_en/b2.py +++ b/campaign/event_20200521_en/b2.py @@ -74,5 +74,5 @@ class Campaign(CampaignBase): return self.battle_default() def battle_5(self): - return self.clear_boss() + return self.fleet_2.clear_boss() diff --git a/campaign/event_20200521_en/b3.py b/campaign/event_20200521_en/b3.py index c1d2dd764..7b5c9c5f0 100644 --- a/campaign/event_20200521_en/b3.py +++ b/campaign/event_20200521_en/b3.py @@ -82,6 +82,6 @@ class Campaign(CampaignBase): return self.battle_default() def battle_5(self): - return self.clear_boss() + return self.fleet_2.clear_boss() diff --git a/config/template.ini b/config/template.ini index 5976140be..902af17c0 100644 --- a/config/template.ini +++ b/config/template.ini @@ -42,8 +42,9 @@ emergency_repair_hole_threshold = 0.6 scout_hp_weights = 1000,1000,1000 low_hp_withdraw_threshold = 0.2 enable_retirement = yes -use_one_click_retirement = no -retire_mode = retire_10 +retire_method = one_click_retire +retire_amount = retire_all +enhance_favourite = no retire_n = yes retire_r = no retire_sr = no diff --git a/module/base/base.py b/module/base/base.py index 921398770..0b30f1696 100644 --- a/module/base/base.py +++ b/module/base/base.py @@ -56,9 +56,12 @@ class ModuleBase: self.device.click(button) return appear - def wait_until_appear(self, button, offset=0): + def wait_until_appear(self, button, offset=0, skip_first_screenshot=False): while 1: - self.device.screenshot() + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() if self.appear(button, offset=offset): break diff --git a/module/base/timer.py b/module/base/timer.py index a6dfe9dea..66ba8fa00 100644 --- a/module/base/timer.py +++ b/module/base/timer.py @@ -23,7 +23,7 @@ def future_time(string): string (str): Such as 14:59. Returns: - datetime.datetime: Time with given hour, minute, second in the future. + datetime.datetime: Time with given hour, minute in the future. """ hour, minute = [int(x) for x in string.split(':')] future = datetime.now().replace(hour=hour, minute=minute, second=0, microsecond=0) @@ -31,6 +31,20 @@ def future_time(string): return future +def past_time(string): + """ + Args: + string (str): Such as 14:59. + + Returns: + datetime.datetime: Time with given hour, minute in the past. + """ + hour, minute = [int(x) for x in string.split(':')] + past = datetime.now().replace(hour=hour, minute=minute, second=0, microsecond=0) + past = past - timedelta(days=1) if past > datetime.now() else past + return past + + def future_time_range(string): """ Args: diff --git a/module/campaign/run.py b/module/campaign/run.py index d997f6ab4..045218305 100644 --- a/module/campaign/run.py +++ b/module/campaign/run.py @@ -107,8 +107,7 @@ class CampaignRun(CampaignUI, Reward, LoginHandler): Returns: bool: If triggered a restart condition. """ - now = datetime.now() - if now.date() != self.start_time.date(): + if self.config.get_server_last_update(since=(0,)) > self.start_time: logger.hr('Triggered restart new day') return True if not self.campaign.config.IGNORE_LOW_EMOTION_WARN: diff --git a/module/config/argparser.py b/module/config/argparser.py index 25c6fde2b..9d339a0ba 100644 --- a/module/config/argparser.py +++ b/module/config/argparser.py @@ -194,8 +194,9 @@ def main(ini_name=''): # 退役选项 retire = setting_parser.add_argument_group('退役设置', '') retire.add_argument('--启用退役', default=default('--启用退役'), choices=['是', '否']) - retire.add_argument('--使用一键退役', default=default('--使用一键退役'), choices=['是', '否']) - retire.add_argument('--退役方案', default=default('--退役方案'), choices=['退役全部', '退役10个']) + retire.add_argument('--退役方案', default=default('--退役方案'), choices=['强化角色', '一键退役', '传统退役']) + retire.add_argument('--退役数量', default=default('--退役数量'), choices=['退役全部', '退役10个']) + retire.add_argument('--强化常用角色', default=default('--强化常用角色'), choices=['是', '否']) rarity = retire.add_argument_group('退役稀有度', '暂不支持舰种选择, 使用一键退役时忽略以下选项') rarity.add_argument('--退役白皮', default=default('--退役白皮'), choices=['是', '否'], help='N') diff --git a/module/config/argparser_en.py b/module/config/argparser_en.py index 7e4f20898..a8f9e702c 100644 --- a/module/config/argparser_en.py +++ b/module/config/argparser_en.py @@ -192,8 +192,9 @@ def main(ini_name=''): # 退役选项 retire = setting_parser.add_argument_group('Retirement settings', '') retire.add_argument('--enable_retirement', default=default('--enable_retirement'), choices=['yes', 'no']) - retire.add_argument('--use_one_click_retirement', default=default('--use_one_click_retirement'), choices=['yes', 'no']) - retire.add_argument('--retire_mode', default=default('--retire_mode'), choices=['retire_all', 'retire_10']) + retire.add_argument('--retire_method', default=default('--retire_method'), choices=['enhance', 'one_click_retire', 'old_retire']) + retire.add_argument('--retire_amount', default=default('--retire_amount'), choices=['retire_all', 'retire_10']) + retire.add_argument('--enhance_favourite', default=default('--enhance_favourite'), choices=['yes', 'no']) rarity = retire.add_argument_group('Retirement rarity', 'The ship type selection is not supported yet. Ignore the following options when using one-key retirement') rarity.add_argument('--retire_n', default=default('--retire_n'), choices=['yes', 'no'], help='N') diff --git a/module/config/config.py b/module/config/config.py index 369ca3b7b..ee9b01791 100644 --- a/module/config/config.py +++ b/module/config/config.py @@ -2,15 +2,16 @@ import codecs import configparser import copy import os +from datetime import timezone import cv2 import numpy as np from PIL import Image +import module.config.server as server from module.base.timer import * from module.config.dictionary import * from module.logger import logger -import module.config.server as server class AzurLaneConfig: @@ -223,8 +224,10 @@ class AzurLaneConfig: """ ENABLE_RETIREMENT = True USE_ONE_CLICK_RETIREMENT = False + RETIREMENT_METHOD = 'one_click_retire' # enhance, old_retire, one_click_retire + ENHANCE_FAVOURITE = False DOCK_FULL_TRIGGERED = False - RETIRE_MODE = '10' # all, 10 + RETIRE_AMOUNT = 'all' # all, 10 RETIRE_N = True RETIRE_R = False RETIRE_SR = False @@ -436,8 +439,9 @@ class AzurLaneConfig: self.SCREEN_SHOT_SAVE_FOLDER_BASE = option['drop_screenshot_folder'] # Retirement self.ENABLE_RETIREMENT = to_bool(option['enable_retirement']) - self.USE_ONE_CLICK_RETIREMENT = to_bool(option['use_one_click_retirement']) - self.RETIRE_MODE = option['retire_mode'].split('_')[1] + self.RETIREMENT_METHOD = option['retire_method'] + self.RETIRE_AMOUNT = option['retire_amount'].split('_')[1] + self.ENHANCE_FAVOURITE = to_bool(option['enhance_favourite']) for r in ['n', 'r', 'sr', 'ssr']: self.__setattr__(f'RETIRE_{r.upper()}', to_bool(option[f'retire_{r}'])) # Clear mode @@ -520,6 +524,30 @@ class AzurLaneConfig: self.C124_NON_S3_WITHDRAW_TOLERANCE = int(option['non_s3_enemy_withdraw_tolerance']) self.C124_AMMO_PICK_UP = int(option['ammo_pick_up_124']) + def get_server_timezone(self): + if self.SERVER == 'en': + return -7 + elif self.SERVER == 'cn': + return 8 + elif self.SERVER == 'jp': + return 9 + else: + return 8 + + def get_server_last_update(self, since): + """ + Args: + since (tuple(int)): Update hour in Azurlane, such as (0, 12, 18,). + + Returns: + datetime.datetime + """ + d = datetime.now(timezone.utc).astimezone() + diff = d.utcoffset() // timedelta(seconds=1) // 3600 - self.get_server_timezone() + since = np.sort((np.array(since) + diff) % 24) + update = sorted([past_time(f'{t}:00') for t in since])[-1] + return update + def record_executed_since(self, option, since): """ Args: @@ -530,10 +558,7 @@ class AzurLaneConfig: bool: If got a record after last game update. """ record = datetime.strptime(self.config.get(*option), self.TIME_FORMAT) - since = np.array(since) - - hour = since[since <= datetime.now().hour][-1] - update = datetime.now().replace(hour=hour, minute=0, second=0, microsecond=0) + update = self.get_server_last_update(since) logger.attr(f'{option[0]}_{option[1]}', f'Record time: {record}') logger.attr(f'{option[0]}_{option[1]}', f'Last update: {update}') diff --git a/module/config/dictionary.py b/module/config/dictionary.py index 8ea286081..9a687f572 100644 --- a/module/config/dictionary.py +++ b/module/config/dictionary.py @@ -77,8 +77,9 @@ dic_true_eng_to_eng = { 'emergency_repair_hole_threshold': 'emergency_repair_hole_threshold', 'low_hp_withdraw_threshold': 'low_hp_withdraw_threshold', 'enable_retirement': 'enable_retirement', - 'use_one_click_retirement': 'use_one_click_retirement', - 'retire_mode': 'retire_mode', + 'retire_method': 'retire_method', + 'retire_amount': 'retire_amount', + 'enhance_favourite': 'enhance_favourite', 'retire_n': 'retire_n', 'retire_r': 'retire_r', 'retire_sr': 'retire_sr', @@ -179,6 +180,9 @@ dic_true_eng_to_eng = { 'avoid_green_face': 'avoid_green_face', 'avoid_yellow_face': 'avoid_yellow_face', 'avoid_red_face': 'avoid_red_face', + 'enhance': 'enhance', + 'one_click_retire': 'one_click_retire', + 'old_retire': 'old_retire', 'retire_all': 'retire_all', 'retire_10': 'retire_10', 'map_100': 'map_100', @@ -255,8 +259,9 @@ dic_chi_to_eng = { '紧急维修全队阈值': 'emergency_repair_hole_threshold', '低血量撤退阈值': 'low_hp_withdraw_threshold', '启用退役': 'enable_retirement', - '使用一键退役': 'use_one_click_retirement', - '退役方案': 'retire_mode', + '退役方案': 'retire_method', + '退役数量': 'retire_amount', + '强化常用角色': 'enhance_favourite', '退役白皮': 'retire_n', '退役蓝皮': 'retire_r', '退役紫皮': 'retire_sr', @@ -357,6 +362,9 @@ dic_chi_to_eng = { '防止绿脸': 'avoid_green_face', '防止黄脸': 'avoid_yellow_face', '防止红脸': 'avoid_red_face', + '强化角色': 'enhance', + '一键退役': 'one_click_retire', + '传统退役': 'old_retire', '退役全部': 'retire_all', '退役10个': 'retire_10', '地图通关': 'map_100', diff --git a/module/equipment/equipment.py b/module/equipment/equipment.py index 303fe1f6c..9d2c6b1cb 100644 --- a/module/equipment/equipment.py +++ b/module/equipment/equipment.py @@ -1,20 +1,25 @@ +from module.base.button import ButtonGrid from module.base.timer import Timer from module.equipment.assets import * from module.handler.info_handler import InfoHandler from module.logger import logger from module.ui.assets import BACK_ARROW +from module.base.utils import color_similarity_2d +import numpy as np SWIPE_DISTANCE = 250 SWIPE_RANDOM_RANGE = (-40, -20, 40, 20) +DETAIL_SIDEBAR = ButtonGrid( + origin=(21, 118), delta=(0, 94.5), button_shape=(60, 75), grid_shape=(1, 5), name='DETAIL_SIDEBAR') class Equipment(InfoHandler): equipment_has_take_on = False - def _view_swipe(self, distance): - swipe_timer = Timer(3, count=3) + def _view_swipe(self, distance, check_button=EQUIPMENT_OPEN): + swipe_timer = Timer(3, count=5) + SWIPE_CHECK.load_color(self.device.image) while 1: - SWIPE_CHECK.load_color(self.device.image) if not swipe_timer.started() or swipe_timer.reached(): swipe_timer.reset() self.device.swipe(vector=(distance, 0), box=SWIPE_AREA.area, random_range=SWIPE_RANDOM_RANGE, @@ -22,32 +27,40 @@ class Equipment(InfoHandler): self.device.screenshot() if SWIPE_CHECK.match(self.device.image): + if swipe_timer.reached(): + import time + from PIL import Image + self.device.image.save(f'{int(time.time() * 1000)}.png') + Image.fromarray(SWIPE_CHECK.image).save(f'{int(time.time() * 1000)}.png') continue - if self.appear(EQUIPMENT_OPEN) and not SWIPE_CHECK.match(self.device.image): + if self.appear(check_button, offset=(30, 30)) and not SWIPE_CHECK.match(self.device.image): break - def _view_next(self): - self._view_swipe(distance=-SWIPE_DISTANCE) + def equip_view_next(self, check_button=EQUIPMENT_OPEN): + self._view_swipe(distance=-SWIPE_DISTANCE, check_button=check_button) - def _view_prev(self): - self._view_swipe(distance=SWIPE_DISTANCE) + def equip_view_prev(self, check_button=EQUIPMENT_OPEN): + self._view_swipe(distance=SWIPE_DISTANCE, check_button=check_button) - def _equip_enter(self, enter): + def equip_enter(self, click_button, check_button=EQUIPMENT_OPEN, long_click=True): enter_timer = Timer(5) while 1: if enter_timer.reached(): - self.device.long_click(enter, duration=(1.5, 1.7)) + if long_click: + self.device.long_click(click_button, duration=(1.5, 1.7)) + else: + self.device.click(click_button) enter_timer.reset() self.device.screenshot() # End - if self.appear(EQUIPMENT_OPEN): + if self.appear(check_button): break - def _equip_exit(self, out): + def equip_quit(self, out): quit_timer = Timer(3) while 1: @@ -64,6 +77,70 @@ class Equipment(InfoHandler): quit_timer.reset() continue + def _equip_sidebar_click(self, index): + """ + Args: + index (int): + 5 for retrofit. + 4 for enhancement. + 3 for limit break. + 2 for gem / equipment. + 1 for detail. + + Returns: + bool: if changed. + """ + current = 0 + total = 0 + + for idx, button in enumerate(DETAIL_SIDEBAR.buttons()): + image = np.array(self.device.image.crop(button.area)) + if np.sum(image[:, :, 0] > 235) > 100: + current = idx + 1 + total = idx + 1 + continue + if np.sum(color_similarity_2d(image, color=(140, 162, 181)) > 221) > 100: + total = idx + 1 + else: + break + if not current: + logger.warning('No ship details sidebar active.') + if total == 4: + current = 5 - current + elif total == 5: + current = 6 - current + else: + logger.warning('Ship details sidebar total count error.') + + logger.attr('Detail_sidebar', f'{current}/{total}') + if current == index: + return False + + self.device.click(DETAIL_SIDEBAR[0, total - index]) + return True + + def equip_sidebar_ensure(self, index, skip_first_screenshot=True): + """ + Args: + index (int): + 5 for retrofit. + 4 for enhancement. + 3 for limit break. + 2 for gem / equipment. + 1 for detail. + """ + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self._equip_sidebar_click(index): + self.device.sleep((0.3, 0.5)) + continue + else: + break + def _equip_take_off_one(self): bar_timer = Timer(5) off_timer = Timer(5) @@ -98,16 +175,16 @@ class Equipment(InfoHandler): fleet (list[int]): list of equipment record. [3, 1, 1, 1, 1, 1] """ logger.hr('Equipment take off') - self._equip_enter(enter) + self.equip_enter(enter) for index in '9'.join([str(x) for x in fleet if x > 0]): index = int(index) if index == 9: - self._view_next() + self.equip_view_next() else: self._equip_take_off_one() - self._equip_exit(out) + self.equip_quit(out) self.equipment_has_take_on = False def _equip_take_on_one(self, index): @@ -148,14 +225,14 @@ class Equipment(InfoHandler): fleet (list[int]): list of equipment record. [3, 1, 1, 1, 1, 1] """ logger.hr('Equipment take on') - self._equip_enter(enter) + self.equip_enter(enter) for index in '9'.join([str(x) for x in fleet if x > 0]): index = int(index) if index == 9: - self._view_next() + self.equip_view_next() else: self._equip_take_on_one(index=index) - self._equip_exit(out) + self.equip_quit(out) self.equipment_has_take_on = True diff --git a/module/map/map_operation.py b/module/map/map_operation.py index 008f801a6..8170bb0f2 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -128,7 +128,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand if self.handle_popup_confirm(): continue - if self.appear_then_click(WITHDRAW, interval=2): + if self.appear_then_click(WITHDRAW, interval=5): continue # End diff --git a/module/retire/assets.py b/module/retire/assets.py index 240c68d89..3e136c789 100644 --- a/module/retire/assets.py +++ b/module/retire/assets.py @@ -6,8 +6,19 @@ from module.base.template import Template COMMON_SHIP_FILTER_DISABLE = Button(area={'cn': (666, 6, 802, 48), 'en': (665, 5, 803, 49)}, color={'cn': (71, 88, 125), 'en': (74, 90, 125)}, button={'cn': (666, 6, 802, 48), 'en': (665, 5, 803, 49)}, file={'cn': './assets/cn/retire/COMMON_SHIP_FILTER_DISABLE.png', 'en': './assets/en/retire/COMMON_SHIP_FILTER_DISABLE.png'}) COMMON_SHIP_FILTER_ENABLE = Button(area={'cn': (666, 6, 802, 48), 'en': (665, 5, 803, 49)}, color={'cn': (182, 145, 96), 'en': (183, 145, 94)}, button={'cn': (666, 6, 802, 48), 'en': (665, 5, 803, 49)}, file={'cn': './assets/cn/retire/COMMON_SHIP_FILTER_ENABLE.png', 'en': './assets/en/retire/COMMON_SHIP_FILTER_ENABLE.png'}) +DOCK_CHECK = Button(area={'cn': (121, 14, 175, 39), 'en': (121, 14, 175, 39)}, color={'cn': (156, 171, 207), 'en': (156, 171, 207)}, button={'cn': (121, 14, 175, 39), 'en': (121, 14, 175, 39)}, file={'cn': './assets/cn/retire/DOCK_CHECK.png', 'en': './assets/cn/retire/DOCK_CHECK.png'}) +DOCK_FILTER = Button(area={'cn': (1099, 5, 1193, 48), 'en': (1099, 5, 1193, 48)}, color={'cn': (70, 87, 127), 'en': (70, 87, 127)}, button={'cn': (1099, 5, 1193, 48), 'en': (1099, 5, 1193, 48)}, file={'cn': './assets/cn/retire/DOCK_FILTER.png', 'en': './assets/cn/retire/DOCK_FILTER.png'}) +DOCK_FILTER_CONFIRM = Button(area={'cn': (714, 613, 886, 671), 'en': (714, 613, 886, 671)}, color={'cn': (86, 133, 192), 'en': (86, 133, 192)}, button={'cn': (714, 613, 886, 671), 'en': (714, 613, 886, 671)}, file={'cn': './assets/cn/retire/DOCK_FILTER_CONFIRM.png', 'en': './assets/cn/retire/DOCK_FILTER_CONFIRM.png'}) +ENHANCE_CONFIRM = Button(area={'cn': (1127, 602, 1256, 644), 'en': (1127, 602, 1256, 644)}, color={'cn': (197, 146, 83), 'en': (197, 146, 83)}, button={'cn': (1127, 602, 1256, 644), 'en': (1127, 602, 1256, 644)}, file={'cn': './assets/cn/retire/ENHANCE_CONFIRM.png', 'en': './assets/cn/retire/ENHANCE_CONFIRM.png'}) +ENHANCE_FILLED = Button(area={'cn': (728, 440, 781, 454), 'en': (728, 440, 781, 454)}, color={'cn': (156, 138, 127), 'en': (156, 138, 127)}, button={'cn': (728, 440, 781, 454), 'en': (728, 440, 781, 454)}, file={'cn': './assets/cn/retire/ENHANCE_FILLED.png', 'en': './assets/cn/retire/ENHANCE_FILLED.png'}) +ENHANCE_RECOMMEND = Button(area={'cn': (959, 602, 1089, 644), 'en': (959, 602, 1089, 644)}, color={'cn': (85, 131, 190), 'en': (85, 131, 190)}, button={'cn': (959, 602, 1089, 644), 'en': (959, 602, 1089, 644)}, file={'cn': './assets/cn/retire/ENHANCE_RECOMMEND.png', 'en': './assets/cn/retire/ENHANCE_RECOMMEND.png'}) +ENHANCE_RELOAD = Button(area={'cn': (898, 277, 1164, 291), 'en': (898, 277, 1164, 291)}, color={'cn': (251, 231, 94), 'en': (251, 231, 94)}, button={'cn': (898, 277, 1164, 291), 'en': (898, 277, 1164, 291)}, file={'cn': './assets/cn/retire/ENHANCE_RELOAD.png', 'en': './assets/cn/retire/ENHANCE_RELOAD.png'}) EQUIP_CONFIRM = Button(area={'cn': (871, 516, 1044, 573), 'en': (870, 515, 1045, 574)}, color={'cn': (95, 143, 203), 'en': (103, 150, 208)}, button={'cn': (871, 516, 1044, 573), 'en': (870, 515, 1045, 574)}, file={'cn': './assets/cn/retire/EQUIP_CONFIRM.png', 'en': './assets/en/retire/EQUIP_CONFIRM.png'}) EQUIP_CONFIRM_2 = Button(area={'cn': (720, 541, 893, 598), 'en': (719, 540, 894, 600)}, color={'cn': (94, 142, 202), 'en': (108, 153, 208)}, button={'cn': (720, 541, 893, 598), 'en': (719, 540, 894, 600)}, file={'cn': './assets/cn/retire/EQUIP_CONFIRM_2.png', 'en': './assets/en/retire/EQUIP_CONFIRM_2.png'}) +FILTER_INDEX_ALL_OFF = Button(area={'cn': (285, 514, 421, 554), 'en': (285, 514, 421, 554)}, color={'cn': (124, 137, 154), 'en': (124, 137, 154)}, button={'cn': (285, 514, 421, 554), 'en': (285, 514, 421, 554)}, file={'cn': './assets/cn/retire/FILTER_INDEX_ALL_OFF.png', 'en': './assets/cn/retire/FILTER_INDEX_ALL_OFF.png'}) +FILTER_INDEX_ALL_ON = Button(area={'cn': (285, 514, 421, 554), 'en': (285, 514, 421, 554)}, color={'cn': (88, 122, 173), 'en': (88, 122, 173)}, button={'cn': (285, 514, 421, 554), 'en': (285, 514, 421, 554)}, file={'cn': './assets/cn/retire/FILTER_INDEX_ALL_ON.png', 'en': './assets/cn/retire/FILTER_INDEX_ALL_ON.png'}) +FILTER_INDEX_ENHANCEMENT_OFF = Button(area={'cn': (757, 514, 893, 554), 'en': (757, 514, 893, 554)}, color={'cn': (132, 144, 160), 'en': (132, 144, 160)}, button={'cn': (757, 514, 893, 554), 'en': (757, 514, 893, 554)}, file={'cn': './assets/cn/retire/FILTER_INDEX_ENHANCEMENT_OFF.png', 'en': './assets/cn/retire/FILTER_INDEX_ENHANCEMENT_OFF.png'}) +FILTER_INDEX_ENHANCEMENT_ON = Button(area={'cn': (757, 514, 893, 554), 'en': (757, 514, 893, 554)}, color={'cn': (98, 130, 178), 'en': (98, 130, 178)}, button={'cn': (757, 514, 893, 554), 'en': (757, 514, 893, 554)}, file={'cn': './assets/cn/retire/FILTER_INDEX_ENHANCEMENT_ON.png', 'en': './assets/cn/retire/FILTER_INDEX_ENHANCEMENT_ON.png'}) GET_ITEMS_1_RETIREMENT_SAVE = Button(area={'cn': (1031, 656, 1063, 688), 'en': (1031, 656, 1063, 688)}, color={'cn': (49, 44, 54), 'en': (49, 44, 54)}, button={'cn': (1031, 656, 1063, 688), 'en': (1031, 656, 1063, 688)}, file={'cn': './assets/cn/retire/GET_ITEMS_1_RETIREMENT_SAVE.png', 'en': './assets/en/retire/GET_ITEMS_1_RETIREMENT_SAVE.png'}) IN_RETIREMENT_CHECK = Button(area={'cn': (854, 641, 1027, 698), 'en': (853, 640, 1028, 700)}, color={'cn': (184, 99, 89), 'en': (184, 101, 92)}, button={'cn': (854, 641, 1027, 698), 'en': (853, 640, 1028, 700)}, file={'cn': './assets/cn/retire/IN_RETIREMENT_CHECK.png', 'en': './assets/en/retire/IN_RETIREMENT_CHECK.png'}) ONE_CLICK_RETIREMENT = Button(area={'cn': (639, 641, 811, 698), 'en': (637, 640, 813, 700)}, color={'cn': (94, 138, 194), 'en': (93, 136, 192)}, button={'cn': (639, 641, 811, 698), 'en': (637, 640, 813, 700)}, file={'cn': './assets/cn/retire/ONE_CLICK_RETIREMENT.png', 'en': './assets/en/retire/ONE_CLICK_RETIREMENT.png'}) @@ -16,6 +27,7 @@ RETIRE_APPEAR_2 = Button(area={'cn': (553, 492, 727, 550), 'en': (553, 481, 727, RETIRE_APPEAR_3 = Button(area={'cn': (753, 492, 927, 550), 'en': (753, 481, 927, 541)}, color={'cn': (94, 143, 202), 'en': (109, 152, 204)}, button={'cn': (753, 492, 927, 550), 'en': (753, 481, 927, 541)}, file={'cn': './assets/cn/retire/RETIRE_APPEAR_3.png', 'en': './assets/en/retire/RETIRE_APPEAR_3.png'}) SHIP_CONFIRM = Button(area={'cn': (1069, 641, 1241, 698), 'en': (1068, 640, 1243, 700)}, color={'cn': (84, 131, 190), 'en': (92, 136, 192)}, button={'cn': (1069, 641, 1241, 698), 'en': (1068, 640, 1243, 700)}, file={'cn': './assets/cn/retire/SHIP_CONFIRM.png', 'en': './assets/en/retire/SHIP_CONFIRM.png'}) SHIP_CONFIRM_2 = Button(area={'cn': (928, 617, 1100, 674), 'en': (927, 615, 1101, 675)}, color={'cn': (95, 143, 203), 'en': (104, 149, 206)}, button={'cn': (928, 617, 1100, 674), 'en': (927, 615, 1101, 675)}, file={'cn': './assets/cn/retire/SHIP_CONFIRM_2.png', 'en': './assets/en/retire/SHIP_CONFIRM_2.png'}) +SHIP_DETAIL_CHECK = Button(area={'cn': (121, 15, 230, 40), 'en': (121, 15, 230, 40)}, color={'cn': (152, 168, 206), 'en': (152, 168, 206)}, button={'cn': (121, 15, 230, 40), 'en': (121, 15, 230, 40)}, file={'cn': './assets/cn/retire/SHIP_DETAIL_CHECK.png', 'en': './assets/cn/retire/SHIP_DETAIL_CHECK.png'}) SORTING_CLICK = Button(area={'cn': (1004, 14, 1096, 42), 'en': (1002, 12, 1058, 44)}, color={'cn': (49, 54, 67), 'en': (47, 51, 65)}, button={'cn': (1004, 14, 1096, 42), 'en': (1002, 12, 1058, 44)}, file={'cn': './assets/cn/retire/SORTING_CLICK.png', 'en': './assets/en/retire/SORTING_CLICK.png'}) SORT_ASC = Button(area={'cn': (1014, 22, 1019, 26), 'en': (1014, 22, 1019, 26)}, color={'cn': (189, 207, 231), 'en': (189, 207, 231)}, button={'cn': (1014, 22, 1019, 26), 'en': (1014, 22, 1019, 26)}, file={'cn': './assets/cn/retire/SORT_ASC.png', 'en': './assets/en/retire/SORT_ASC.png'}) SORT_DESC = Button(area={'cn': (1014, 29, 1019, 33), 'en': (1014, 29, 1019, 33)}, color={'cn': (189, 207, 231), 'en': (189, 207, 231)}, button={'cn': (1014, 29, 1019, 33), 'en': (1014, 29, 1019, 33)}, file={'cn': './assets/cn/retire/SORT_DESC.png', 'en': './assets/en/retire/SORT_DESC.png'}) diff --git a/module/retire/dock.py b/module/retire/dock.py new file mode 100644 index 000000000..027a242aa --- /dev/null +++ b/module/retire/dock.py @@ -0,0 +1,59 @@ +from module.base.button import ButtonGrid +from module.base.switch import Switch +from module.equipment.equipment import Equipment +from module.retire.assets import * +from module.ui.ui import UI + +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) + +favourite_filter = Switch('Favourite_filter') +favourite_filter.add_status('on', check_button=COMMON_SHIP_FILTER_ENABLE) +favourite_filter.add_status('off', check_button=COMMON_SHIP_FILTER_DISABLE) + +filter_index_enhanceable = Switch('Filter_index_enhanceable') +filter_index_enhanceable.add_status('on', check_button=FILTER_INDEX_ENHANCEMENT_ON) +filter_index_enhanceable.add_status('off', check_button=FILTER_INDEX_ENHANCEMENT_OFF) + +filter_index_all = Switch('Filter_index_all') +filter_index_all.add_status('on', check_button=FILTER_INDEX_ALL_ON) +filter_index_all.add_status('off', check_button=FILTER_INDEX_ALL_OFF) + + +CARD_GRIDS = ButtonGrid( + origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 204), grid_shape=(7, 2), name='CARD') +CARD_RARITY_GRIDS = ButtonGrid( + origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 5), grid_shape=(7, 2), name='RARITY') + + +class Dock(UI, Equipment): + def handle_dock_cards_loading(self): + self.device.sleep((1, 1.5)) + + def dock_favourite_set(self, enable=False): + if favourite_filter.set('on' if enable else 'off', main=self): + self.handle_dock_cards_loading() + + def _dock_quit_check_func(self): + return not self.appear(DOCK_CHECK, offset=(20, 20)) + + def dock_quit(self): + self.ui_back(check_button=self._dock_quit_check_func, skip_first_screenshot=True) + + def dock_sort_method_dsc_set(self, enable=True): + if dock_sorting.set('on' if enable else 'off', main=self): + self.handle_dock_cards_loading() + + def dock_filter_enter(self): + self.ui_click(DOCK_FILTER, check_button=DOCK_FILTER_CONFIRM, skip_first_screenshot=True) + + def dock_filter_confirm(self): + self.ui_click(DOCK_FILTER_CONFIRM, check_button=DOCK_FILTER, skip_first_screenshot=True) + self.handle_dock_cards_loading() + + def dock_filter_index_enhance_set(self, enable): + filter_index_enhanceable.set('on' if enable else 'off', main=self) + + def dock_filter_index_all_set(self, enable): + filter_index_all.set('on' if enable else 'off', main=self) diff --git a/module/retire/enhancement.py b/module/retire/enhancement.py new file mode 100644 index 000000000..925adca46 --- /dev/null +++ b/module/retire/enhancement.py @@ -0,0 +1,136 @@ +import numpy as np + +from module.base.utils import color_bar_percentage +from module.combat.assets import GET_ITEMS_1 +from module.exception import ScriptError +from module.logger import logger +from module.retire.assets import * +from module.retire.dock import Dock, CARD_GRIDS + + +class Enhancement(Dock): + @property + def _retire_amount(self): + if self.config.RETIRE_AMOUNT == 'all': + return 2000 + if self.config.RETIRE_AMOUNT == '10': + return 10 + return 10 + + def _enhance_enter(self, favourite=False): + if favourite: + self.dock_favourite_set(enable=True) + + self.dock_filter_enter() + self.dock_filter_index_enhance_set(enable=True) + self.dock_filter_confirm() + + self.equip_enter(CARD_GRIDS[(0, 0)], check_button=SHIP_DETAIL_CHECK, long_click=False) + + def _enhance_quit(self): + self.equip_quit(DOCK_FILTER) + self.dock_favourite_set(enable=False) + self.dock_filter_enter() + self.dock_filter_index_all_set(enable=True) + self.dock_filter_confirm() + + def _enhance_confirm(self): + executed = False + while 1: + self.device.screenshot() + + # if self.appear_then_click(ENHANCE_CONFIRM, offset=(30, 30), interval=3): + # continue + if self.appear_then_click(EQUIP_CONFIRM, offset=(30, 30), interval=3): + continue + if self.appear_then_click(EQUIP_CONFIRM_2, offset=(30, 30), interval=3): + continue + if self.appear(GET_ITEMS_1, interval=2): + self.device.click(GET_ITEMS_1_RETIREMENT_SAVE) + self.interval_reset(ENHANCE_CONFIRM) + executed = True + continue + + # End + if executed and self.appear(ENHANCE_CONFIRM, offset=(30, 30)): + self.ensure_no_info_bar() + break + + def _enhance_choose(self, skip_first_screenshot=True): + """ + Page require: page_ship_enhance, without info_bar + """ + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(EQUIP_CONFIRM, offset=(30, 30)): + return True + + self.equip_sidebar_ensure(index=4) + self.wait_until_appear(ENHANCE_RECOMMEND, offset=(5, 5), skip_first_screenshot=True) + + status = color_bar_percentage(self.device.image, area=ENHANCE_RELOAD.area, prev_color=(231, 178, 74)) + logger.attr('Reload_enhanced', f'{int(status * 100)}%') + choose = np.sum(np.array(self.device.image.crop(ENHANCE_FILLED.area)) > 200) > 100 + + if self.info_bar_count(): + if status > 0.98: + logger.info('Fully enhanced for this ship') + self.equip_view_next(check_button=ENHANCE_RECOMMEND) + self.ensure_no_info_bar() + continue + else: + if choose: + logger.info('Unable to enhance this ship') + self.equip_view_next(check_button=ENHANCE_RECOMMEND) + self.ensure_no_info_bar() + continue + else: + logger.info('Enhancement material exhausted') + return False + + if self.appear_then_click(ENHANCE_RECOMMEND, offset=(5, 5), interval=2): + self.device.sleep(0.3) + self.appear_then_click(ENHANCE_CONFIRM) + + def enhance_ships(self, favourite=None): + """Page require: page_dock + + Args: + favourite (bool): + + Returns: + int: total enhanced + """ + if favourite is None: + favourite = self.config.ENHANCE_FAVOURITE + + logger.hr('Enhancement') + logger.info(f'Favourite={favourite}') + self._enhance_enter(favourite=favourite) + total = 0 + + while 1: + if not self._enhance_choose(): + break + self._enhance_confirm() + total += 10 + if total >= self._retire_amount: + break + + self._enhance_quit() + return total + + def _enhance_handler(self): + self.ui_click(RETIRE_APPEAR_3, check_button=DOCK_FILTER, skip_first_screenshot=True) + self.handle_dock_cards_loading() + + total = self.enhance_ships() + + self.dock_quit() + self.config.DOCK_FULL_TRIGGERED = True + + return total diff --git a/module/retire/retirement.py b/module/retire/retirement.py index 94b690724..5ee4e0e80 100644 --- a/module/retire/retirement.py +++ b/module/retire/retirement.py @@ -3,13 +3,14 @@ from module.base.decorator import Config from module.base.utils import get_color, color_similar from module.combat.assets import GET_ITEMS_1 from module.exception import ScriptError -from module.handler.info_handler import InfoHandler from module.logger import logger from module.retire.assets import * -from module.ui.ui import UI +from module.retire.enhancement import Enhancement -CARD_GRIDS = ButtonGrid(origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 204), grid_shape=(7, 2), name='CARD') -CARD_RARITY_GRIDS = ButtonGrid(origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 5), grid_shape=(7, 2), name='RARITY') +CARD_GRIDS = ButtonGrid( + origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 204), grid_shape=(7, 2), name='CARD') +CARD_RARITY_GRIDS = ButtonGrid( + origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 5), grid_shape=(7, 2), name='RARITY') CARD_RARITY_COLORS = { 'N': (174, 176, 187), @@ -20,9 +21,8 @@ CARD_RARITY_COLORS = { } -class Retirement(UI, InfoHandler): - def _handle_retirement_cards_loading(self): - self.device.sleep((1, 1.5)) +class Retirement(Enhancement): + _unable_to_enhance = False def _retirement_choose(self, amount=10, target_rarity=('N',)): """ @@ -80,7 +80,7 @@ class Retirement(UI, InfoHandler): if current != method: logger.info(f'Sorting set to {method}') self.device.click(SORTING_CLICK) - self._handle_retirement_cards_loading() + self.handle_dock_cards_loading() self.device.screenshot() return True else: @@ -106,7 +106,7 @@ class Retirement(UI, InfoHandler): if current != enable: logger.info(f'Common ship filter set to {enable}') self.device.click(COMMON_SHIP_FILTER_ENABLE) - self._handle_retirement_cards_loading() + self.handle_dock_cards_loading() self.device.screenshot() return True else: @@ -116,7 +116,7 @@ class Retirement(UI, InfoHandler): executed = False while 1: self.device.screenshot() - if self.config.RETIRE_SR or self.config.RETIRE_SSR or self.config.USE_ONE_CLICK_RETIREMENT: + if self.config.RETIRE_SR or self.config.RETIRE_SSR or self.config.RETIREMENT_METHOD == 'one_click_retire': if self.handle_popup_confirm(): continue if self.appear_then_click(SHIP_CONFIRM, offset=30, interval=2): @@ -152,14 +152,6 @@ class Retirement(UI, InfoHandler): def _retirement_quit(self): self.ui_back(check_button=self._retirement_quit_check_func, skip_first_screenshot=True) - @property - def _retire_amount(self): - if self.config.RETIRE_MODE == 'all': - return 2000 - if self.config.RETIRE_MODE == '10': - return 10 - return 10 - @property def _retire_rarity(self): rarity = set() @@ -173,7 +165,7 @@ class Retirement(UI, InfoHandler): rarity.add('SSR') return rarity - @Config.when(USE_ONE_CLICK_RETIREMENT=True) + @Config.when(RETIREMENT_METHOD='one_click_retire') def retire_ships(self, amount=None, rarity=None): logger.hr('Retirement') logger.info('Using one click retirement.') @@ -206,7 +198,7 @@ class Retirement(UI, InfoHandler): logger.info(f'Total retired round: {total // 10}') return total - @Config.when(USE_ONE_CLICK_RETIREMENT=False) + @Config.when(RETIREMENT_METHOD='old_retire') def retire_ships(self, amount=None, rarity=None): """ Args: @@ -242,21 +234,41 @@ class Retirement(UI, InfoHandler): logger.info(f'Total retired: {total}') return total - def handle_retirement(self, amount=None, rarity=None): + def handle_retirement(self): if not self.config.ENABLE_RETIREMENT: return False if not self.retirement_appear(): return False - self.ui_click(RETIRE_APPEAR_1, check_button=IN_RETIREMENT_CHECK, skip_first_screenshot=True) - self._handle_retirement_cards_loading() + if self._unable_to_enhance: + self.config.RETIREMENT_METHOD = 'one_click_retire' + total = self._retire_handler() + self.config.RETIREMENT_METHOD = 'enhance' + self._unable_to_enhance = False + if not total: + logger.warning('No ship retired, exit') + raise ScriptError('No ship retired, exit') + elif 'retire' in self.config.RETIREMENT_METHOD or self._unable_to_enhance: + total = self._retire_handler() + self._unable_to_enhance = False + if not total: + logger.warning('No ship retired, exit') + raise ScriptError('No ship retired, exit') + else: + total = self._enhance_handler() + if not total: + logger.info('No ship to enhance, but dock full, will try retire') + self._unable_to_enhance = True - total = self.retire_ships(amount=amount, rarity=rarity) + return True + + def _retire_handler(self): + self.ui_click(RETIRE_APPEAR_1, check_button=IN_RETIREMENT_CHECK, skip_first_screenshot=True) + self.handle_dock_cards_loading() + + total = self.retire_ships() self._retirement_quit() self.config.DOCK_FULL_TRIGGERED = True - if total == 0: - logger.warning('No ship retired, exit') - raise ScriptError('No ship retired, exit') - return True + return total diff --git a/module/reward/commission.py b/module/reward/commission.py index de375c9ad..b5c8dafea 100644 --- a/module/reward/commission.py +++ b/module/reward/commission.py @@ -32,19 +32,19 @@ dictionary_cn = { 'urgent_ship': ['观舰'] } dictionary_en = { - 'major_comm': ['SelfTrainingl', 'Defense Exercise', 'Research Mission', 'Prep', 'Class', 'Cargo Transport'], + 'major_comm': ['SelfTraining', 'DefenseExercise', 'ResearchMission', 'Prep', 'Class', 'CargoTransport'], 'daily_comm': ['Daily', 'Awakening'], - 'extra_drill': ['Sailing', 'Defense Patrol', 'Buoy'], - 'extra_part': ['veinprotectoncommisionll', 'Forestprtectoncommisionl', 'Forestprotectoncommisionll'], - 'extra_cube': ['Exercise'], - 'extra_oil': ['oilextraction', 'FleetCargoTransport', 'oilExtractianl', '', 'oilExtractiaonll'], - 'extra_book': ['LargeMerchantEscort'], - 'urgent_drill': ['Cargo Defense', 'Scouts', 'Force', 'Elites', 'FrontierDefensePatrol'], - 'urgent_part': ['Lavella', 'Maui', 'Rendova', 'AidingWongbanna'], - 'urgent_book': ['Tyrant', 'Poro', 'Makira', 'Kapolo', 'Manne ', 'St.', 'Isle', 'Kotlin'], - 'urgent_box': ['Gear Transport', 'Handover'], - 'urgent_cube': ['MerchantRescuel', 'Attack'], - 'urgent_gem': ['VIP ', 'Holiday', 'Patrol Escort'], + 'extra_drill': ['Sailing', 'DefensePatrol', 'Buoy'], + 'extra_part': ['Protection'], + 'extra_cube': ['FleetExercise', 'EscortExercise', 'FleetCargo', 'CombatExercise'], + 'extra_oil': ['oil'], + 'extra_book': ['MerchantEscort'], + 'urgent_drill': ['CargoDefense', 'Destroy'], + 'urgent_part': ['Lavella', 'Maui', 'Rendova', 'banna'], + 'urgent_book': ['Tyrant', 'Poro', 'Makira', 'Kapolo', 'Manne ', 'Mary', 'Isle', 'Kotlin'], + 'urgent_box': ['Gear', 'Handover'], + 'urgent_cube': ['MerchantRescue', 'Attack'], + 'urgent_gem': ['VIP ', 'Holiday', 'PatrolEscort'], 'urgent_ship': ['Launch'] } @@ -181,7 +181,7 @@ class Commission: Returns: timedelta: datetime.timedelta instance. """ - string = string.replace('D', '0').replace(' ', '').replace('-', '') # Poor OCR + string = string.replace('D', '0') # Poor OCR result = re.search('(\d+):(\d+):(\d+)', string) if not result: logger.warning(f'Invalid time string: {string}') @@ -200,6 +200,7 @@ class Commission: Returns: str: Commission genre, such as 'urgent_gem'. """ + string = string.replace(' ', '').replace('-', '') for key, value in dictionary_en.items(): for keyword in value: if keyword in string: