mirror of
https://github.com/LmeSzinc/AzurLaneAutoScript.git
synced 2025-01-08 12:07:36 +08:00
581 lines
22 KiB
Python
581 lines
22 KiB
Python
from module.base.button import ButtonGrid
|
|
from module.base.timer import Timer
|
|
from module.base.utils import color_similar, get_color, resize
|
|
from module.combat.assets import GET_ITEMS_1
|
|
from module.exception import RequestHumanTakeover, ScriptError
|
|
from module.handler.assets import AUTO_SEARCH_MAP_OPTION_OFF, AUTO_SEARCH_MAP_OPTION_ON
|
|
from module.logger import logger
|
|
from module.retire.assets import *
|
|
from module.retire.enhancement import Enhancement
|
|
from module.retire.scanner import ShipScanner
|
|
from module.retire.setting import QuickRetireSettingHandler
|
|
from module.ui.scroll import Scroll
|
|
|
|
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),
|
|
'R': (106, 195, 248),
|
|
'SR': (151, 134, 254),
|
|
'SSR': (248, 223, 107)
|
|
# Not support marriage cards.
|
|
}
|
|
|
|
RETIRE_CONFIRM_SCROLL = Scroll(RETIRE_CONFIRM_SCROLL_AREA, color=(74, 77, 110), name='STRATEGIC_SEARCH_SCROLL')
|
|
RETIRE_CONFIRM_SCROLL.color_threshold = 240 # Background color is (66, 72, 77), so default (256-221)=35 is not enough to dintinguish.
|
|
|
|
|
|
class Retirement(Enhancement, QuickRetireSettingHandler):
|
|
_unable_to_enhance = False
|
|
_have_kept_cv = True
|
|
|
|
# From MapOperation
|
|
map_cat_attack_timer = Timer(2)
|
|
|
|
@property
|
|
def retire_keep_common_cv(self):
|
|
return self.config.is_task_enabled('GemsFarming')
|
|
|
|
def _retirement_choose(self, amount=10, target_rarity=('N',)):
|
|
"""
|
|
Args:
|
|
amount (int): Amount of cards retire. 0 to 10.
|
|
target_rarity (tuple(str)): Card rarity. N, R, SR, SSR.
|
|
|
|
Returns:
|
|
int: Amount of cards have retired.
|
|
"""
|
|
cards = []
|
|
rarity = []
|
|
for x, y, button in CARD_RARITY_GRIDS.generate():
|
|
card_color = get_color(image=self.device.image, area=button.area)
|
|
f = False
|
|
for r, rarity_color in CARD_RARITY_COLORS.items():
|
|
|
|
if color_similar(card_color, rarity_color, threshold=15):
|
|
cards.append([x, y])
|
|
rarity.append(r)
|
|
f = True
|
|
|
|
if not f:
|
|
logger.warning(
|
|
f'Unknown rarity color. Grid: ({x}, {y}). Color: {card_color}')
|
|
|
|
logger.info(' '.join([r.rjust(3) for r in rarity[:7]]))
|
|
logger.info(' '.join([r.rjust(3) for r in rarity[7:]]))
|
|
|
|
selected = 0
|
|
for card, r in zip(cards, rarity):
|
|
if r in target_rarity:
|
|
self.device.click(CARD_GRIDS[card])
|
|
self.device.sleep((0.1, 0.15))
|
|
selected += 1
|
|
if selected >= amount:
|
|
break
|
|
return selected
|
|
|
|
def _retirement_confirm(self, skip_first_screenshot=True):
|
|
"""
|
|
Pages:
|
|
in: IN_RETIREMENT_CHECK, and also
|
|
SHIP_CONFIRM_2 if using one_click_retire
|
|
SHIP_CONFIRM if using old_retire
|
|
out: IN_RETIREMENT_CHECK
|
|
"""
|
|
logger.info('Retirement confirm')
|
|
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:
|
|
skip_first_screenshot = False
|
|
else:
|
|
self.device.screenshot()
|
|
|
|
# End
|
|
if timeout.reached():
|
|
# Ships being used by GemsFarming have no equipment to disassemble
|
|
# So `executed` is never set to True, causing infinite loop
|
|
# Handled with dirty timeout, a better fix is required
|
|
logger.warning('Wait _retirement_confirm timeout, assume finished')
|
|
break
|
|
if self.appear(IN_RETIREMENT_CHECK, offset=(20, 20)):
|
|
if executed:
|
|
break
|
|
else:
|
|
timeout.reset()
|
|
|
|
# Click
|
|
# 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
|
|
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
|
|
|
|
def retirement_appear(self):
|
|
return self.appear(RETIRE_APPEAR_1, offset=30) \
|
|
and self.appear(RETIRE_APPEAR_2, offset=30) \
|
|
and self.appear(RETIRE_APPEAR_3, offset=30)
|
|
|
|
def _retirement_quit(self):
|
|
def check_func():
|
|
return not self.appear(IN_RETIREMENT_CHECK, offset=(20, 20)) \
|
|
and not self.appear(DOCK_CHECK, offset=(20, 20))
|
|
|
|
self.ui_back(check_button=check_func, skip_first_screenshot=True)
|
|
|
|
@property
|
|
def _retire_rarity(self):
|
|
rarity = set()
|
|
if self.config.OldRetire_N:
|
|
rarity.add('N')
|
|
if self.config.OldRetire_R:
|
|
rarity.add('R')
|
|
if self.config.OldRetire_SR:
|
|
rarity.add('SR')
|
|
if self.config.OldRetire_SSR:
|
|
rarity.add('SSR')
|
|
return rarity
|
|
|
|
def retire_ships_one_click(self):
|
|
logger.hr('Retirement')
|
|
logger.info('Using one click retirement.')
|
|
# No need to wait, one-click-retire doesn't need to check dock
|
|
self.dock_favourite_set(wait_loading=False)
|
|
self.dock_sort_method_dsc_set(wait_loading=False)
|
|
end = False
|
|
total = 0
|
|
|
|
if self.retire_keep_common_cv:
|
|
self._have_kept_cv = False
|
|
|
|
while 1:
|
|
self.handle_info_bar()
|
|
|
|
# ONE_CLICK_RETIREMENT -> SHIP_CONFIRM_2 or info_bar_count
|
|
skip_first_screenshot = True
|
|
click_count = 0
|
|
while 1:
|
|
if skip_first_screenshot:
|
|
skip_first_screenshot = False
|
|
else:
|
|
self.device.screenshot()
|
|
# End
|
|
if self.appear(SHIP_CONFIRM_2, offset=(30, 30)):
|
|
break
|
|
if self.info_bar_count():
|
|
logger.info('No more ships to retire.')
|
|
end = True
|
|
break
|
|
|
|
# Click
|
|
if click_count >= 7:
|
|
logger.warning('Failed to select ships using ONE_CLICK_RETIREMENT after 7 trial, '
|
|
'probably because game bugged, a re-enter should fix it')
|
|
# Mark as retire finished, higher level will call retires
|
|
end = True
|
|
total = 10
|
|
break
|
|
elif self.appear_then_click(ONE_CLICK_RETIREMENT, offset=(20, 20), interval=2):
|
|
click_count += 1
|
|
continue
|
|
|
|
# info_bar_count
|
|
if end:
|
|
break
|
|
# SHIP_CONFIRM_2 -> IN_RETIREMENT_CHECK
|
|
self._retirement_confirm()
|
|
total += 10
|
|
# if total >= amount:
|
|
# break
|
|
# Always break, since game client retire all once
|
|
break
|
|
|
|
logger.info(f'Total retired round: {total // 10}')
|
|
return total
|
|
|
|
def retire_ships_old(self, amount=None, rarity=None):
|
|
"""
|
|
Args:
|
|
amount (int): Amount of cards retire. 0 to 2000.
|
|
rarity (tuple(str)): Card rarity. N, R, SR, SSR.
|
|
|
|
Returns:
|
|
int: Total retired.
|
|
"""
|
|
if amount is None:
|
|
amount = self._retire_amount
|
|
if rarity is None:
|
|
rarity = self._retire_rarity
|
|
logger.hr('Retirement')
|
|
logger.info(f'Amount={amount}. Rarity={rarity}')
|
|
|
|
# transfer N R SR SSR to filter name
|
|
correspond_name = {
|
|
'N': 'common',
|
|
'R': 'rare',
|
|
'SR': 'elite',
|
|
'SSR': 'super_rare'
|
|
}
|
|
_rarity = [correspond_name[i] for i in rarity]
|
|
self.dock_sort_method_dsc_set(False, wait_loading=False)
|
|
self.dock_favourite_set(False, wait_loading=False)
|
|
self.dock_filter_set(
|
|
sort='level', index='all', faction='all', rarity=_rarity, extra='no_limit')
|
|
|
|
total = 0
|
|
|
|
if self.retire_keep_common_cv:
|
|
self._have_kept_cv = False
|
|
|
|
while amount:
|
|
selected = self._retirement_choose(
|
|
amount=10 if amount > 10 else amount, target_rarity=rarity)
|
|
total += selected
|
|
if selected == 0:
|
|
break
|
|
self.device.screenshot()
|
|
if not self.match_template_color(SHIP_CONFIRM, offset=(30, 30)):
|
|
logger.warning('No ship selected, retrying')
|
|
continue
|
|
|
|
self._retirement_confirm()
|
|
|
|
amount -= selected
|
|
if amount <= 0:
|
|
break
|
|
|
|
self.handle_dock_cards_loading()
|
|
continue
|
|
|
|
self.dock_sort_method_dsc_set(True, wait_loading=False)
|
|
self.dock_filter_set()
|
|
logger.info(f'Total retired: {total}')
|
|
return total
|
|
|
|
def retire_gems_farming_flagships(self, keep_one=True) -> int:
|
|
"""
|
|
Retire abandoned flagships of GemsFarming.
|
|
Common CV whose level > 1, fleet is none and status is free
|
|
will be regarded as targets.
|
|
"""
|
|
logger.info('Retire abandoned flagships of GemsFarming')
|
|
|
|
gems_farming_enable: bool = self.config.is_task_enabled('GemsFarming')
|
|
if not gems_farming_enable:
|
|
logger.info('Not in GemsFarming, skip')
|
|
return 0
|
|
|
|
self.dock_favourite_set(wait_loading=False)
|
|
self.dock_sort_method_dsc_set(wait_loading=False)
|
|
self.dock_filter_set(index='cv', rarity='common', extra='not_level_max', sort='level')
|
|
|
|
scanner = ShipScanner(
|
|
rarity='common', fleet=0, status='free', level=(2, 100))
|
|
scanner.disable('emotion')
|
|
|
|
total = 0
|
|
_ = self._have_kept_cv
|
|
self._have_kept_cv = True
|
|
|
|
skip_first_screenshot = True
|
|
while 1:
|
|
if skip_first_screenshot:
|
|
skip_first_screenshot = False
|
|
else:
|
|
self.device.screenshot()
|
|
|
|
self.handle_info_bar()
|
|
ships = scanner.scan(self.device.image)
|
|
if not ships:
|
|
# exit if nothing can be retired
|
|
break
|
|
if keep_one:
|
|
if len(ships) < 2:
|
|
break
|
|
else:
|
|
# Try to keep the one with the lowest level
|
|
ships.sort(key=lambda s: -s.level)
|
|
ships = ships[:-1]
|
|
|
|
for ship in ships:
|
|
self.device.click(ship.button)
|
|
self.device.sleep((0.1, 0.15))
|
|
total += 1
|
|
|
|
self._retirement_confirm()
|
|
|
|
# Quick exit if there's only a few CV to retire
|
|
if len(ships) < 10:
|
|
break
|
|
|
|
self._have_kept_cv = _
|
|
# No need to wait, retire finished, just about to exit
|
|
self.dock_filter_set(wait_loading=False)
|
|
|
|
return total
|
|
|
|
def handle_retirement(self):
|
|
"""
|
|
Returns:
|
|
bool: If retired.
|
|
"""
|
|
if self._unable_to_enhance:
|
|
if self.appear_then_click(RETIRE_APPEAR_1, offset=(20, 20), interval=3):
|
|
self.interval_clear(IN_RETIREMENT_CHECK)
|
|
self.interval_reset([AUTO_SEARCH_MAP_OPTION_OFF, AUTO_SEARCH_MAP_OPTION_ON])
|
|
self.map_cat_attack_timer.reset()
|
|
return False
|
|
if self.appear(IN_RETIREMENT_CHECK, offset=(20, 20), interval=10):
|
|
self._retire_handler(mode='one_click_retire')
|
|
self._unable_to_enhance = False
|
|
self.interval_reset(IN_RETIREMENT_CHECK)
|
|
self.map_cat_attack_timer.reset()
|
|
return True
|
|
elif self.config.Retirement_RetireMode == 'enhance':
|
|
if self.appear_then_click(RETIRE_APPEAR_3, offset=(20, 20), interval=3):
|
|
self.interval_clear(DOCK_CHECK)
|
|
self.interval_reset([AUTO_SEARCH_MAP_OPTION_OFF, AUTO_SEARCH_MAP_OPTION_ON])
|
|
self.map_cat_attack_timer.reset()
|
|
return False
|
|
if self.appear(DOCK_CHECK, offset=(20, 20), interval=10):
|
|
self.handle_dock_cards_loading()
|
|
total, remain = self._enhance_handler()
|
|
if not total:
|
|
logger.info(
|
|
'No ship to enhance, but dock full, will try retire')
|
|
self._unable_to_enhance = True
|
|
logger.info(f'The remaining spare dock amount is {remain}')
|
|
if remain < 3:
|
|
logger.info('Too few spare docks, retire next time')
|
|
self._unable_to_enhance = True
|
|
self.interval_reset(DOCK_CHECK)
|
|
self.map_cat_attack_timer.reset()
|
|
return True
|
|
else:
|
|
if self.appear_then_click(RETIRE_APPEAR_1, offset=(20, 20), interval=3):
|
|
self.interval_clear(IN_RETIREMENT_CHECK)
|
|
self.interval_reset([AUTO_SEARCH_MAP_OPTION_OFF, AUTO_SEARCH_MAP_OPTION_ON])
|
|
self.map_cat_attack_timer.reset()
|
|
return False
|
|
if self.appear(IN_RETIREMENT_CHECK, offset=(20, 20), interval=10):
|
|
self._retire_handler()
|
|
self._unable_to_enhance = False
|
|
self.interval_reset(IN_RETIREMENT_CHECK)
|
|
self.map_cat_attack_timer.reset()
|
|
return True
|
|
|
|
return False
|
|
|
|
def _retire_handler(self, mode=None):
|
|
"""
|
|
Args:
|
|
mode (str): `one_click_retire` or `old_retire`
|
|
|
|
Returns:
|
|
int: Amount of retired ships
|
|
|
|
Pages:
|
|
in: IN_RETIREMENT_CHECK
|
|
out: the page before retirement popup
|
|
"""
|
|
if mode is None:
|
|
mode = self.config.Retirement_RetireMode
|
|
|
|
if mode == 'one_click_retire':
|
|
total = self.retire_ships_one_click()
|
|
if not total:
|
|
logger.warning(
|
|
'No ship retired, trying to reset dock filter and disable favourite, then retire again')
|
|
self.dock_favourite_set(False, wait_loading=False)
|
|
self.dock_filter_set()
|
|
total = self.retire_ships_one_click()
|
|
if self.server_support_quick_retire_setting_fallback():
|
|
# Some users may have already set filter_5='all', try with it first
|
|
if not total:
|
|
logger.warning('No ship retired, trying to reset the first 4 quick retire settings')
|
|
self.quick_retire_setting_set(filter_5=None)
|
|
total = self.retire_ships_one_click()
|
|
if not total:
|
|
logger.warning('No ship retired, trying to reset quick retire settings to "keep_limit_break"')
|
|
self.quick_retire_setting_set(filter_5='keep_limit_break')
|
|
total = self.retire_ships_one_click()
|
|
if not total and self.config.OneClickRetire_KeepLimitBreak == 'do_not_keep':
|
|
logger.warning('No ship retired, trying to reset quick retire settings to "all"')
|
|
self.quick_retire_setting_set('all')
|
|
total = self.retire_ships_one_click()
|
|
total += self.retire_gems_farming_flagships(keep_one=total > 0)
|
|
if not total:
|
|
logger.critical('No ship retired')
|
|
logger.critical('Please configure your "Quick Retire Options" in game, '
|
|
'make sure it can select ships to retire')
|
|
raise RequestHumanTakeover
|
|
elif mode == 'old_retire':
|
|
self.handle_dock_cards_loading()
|
|
total = self.retire_ships_old()
|
|
total += self.retire_gems_farming_flagships()
|
|
if not total:
|
|
logger.critical('No ship retired')
|
|
logger.critical('Please configure your retirement settings in Alas, '
|
|
'make sure it can select ships to retire')
|
|
raise RequestHumanTakeover
|
|
else:
|
|
raise ScriptError(
|
|
f'Unknown retire mode: {self.config.Retirement_RetireMode}')
|
|
|
|
self._retirement_quit()
|
|
self.config.DOCK_FULL_TRIGGERED = True
|
|
|
|
return total
|
|
|
|
def _retire_select_one(self, button, skip_first_screenshot=True):
|
|
"""
|
|
Args:
|
|
button (Button): Ship button to select
|
|
skip_first_screenshot:
|
|
"""
|
|
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:
|
|
skip_first_screenshot = False
|
|
else:
|
|
self.device.screenshot()
|
|
|
|
# End
|
|
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=2):
|
|
self.device.click(button)
|
|
count += 1
|
|
continue
|
|
|
|
def retirement_get_common_rarity_cv_in_page(self):
|
|
"""
|
|
Returns:
|
|
Button:
|
|
"""
|
|
if self.config.GemsFarming_CommonCV == 'any':
|
|
for common_cv_name in ['BOGUE', 'HERMES', 'LANGLEY', 'RANGER']:
|
|
template = globals()[f'TEMPLATE_{common_cv_name}']
|
|
sim, button = template.match_result(
|
|
resize(self.device.image, size=(1189, 669)))
|
|
|
|
if sim > self.config.COMMON_CV_THRESHOLD:
|
|
return Button(button=tuple(_ * 155 // 144 for _ in button.button), area=button.area,
|
|
color=button.color,
|
|
name=f'TEMPLATE_{common_cv_name}_RETIRE')
|
|
|
|
return None
|
|
else:
|
|
|
|
template = globals()[
|
|
f'TEMPLATE_{self.config.GemsFarming_CommonCV.upper()}']
|
|
sim, button = template.match_result(
|
|
resize(self.device.image, size=(1189, 669)))
|
|
|
|
if sim > self.config.COMMON_CV_THRESHOLD:
|
|
return Button(button=tuple(_ * 155 // 144 for _ in button.button), area=button.area, color=button.color,
|
|
name=f'TEMPLATE_{self.config.GemsFarming_CommonCV.upper()}_RETIRE')
|
|
|
|
return None
|
|
|
|
def retirement_get_common_rarity_cv(self, skip_first_screenshot=False):
|
|
"""
|
|
Args:
|
|
skip_first_screenshot:
|
|
|
|
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')
|