Add: Awaken ships
BIN
assets/cn/awaken/AWAKENING.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
assets/cn/awaken/AWAKEN_CANCEL.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/cn/awaken/AWAKEN_CONFIRM.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/cn/awaken/AWAKEN_FINISH.BUTTON.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/cn/awaken/AWAKEN_FINISH.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
assets/cn/awaken/COST_ARRAY.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/cn/awaken/COST_CHIP.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/cn/awaken/COST_COIN.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
assets/cn/awaken/LEVEL_UP.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
assets/cn/awaken/OCR_SHIP_LEVEL.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/cn/awaken/SHIP_LEVEL_CHECK.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
16
module/awaken/assets.py
Normal file
@ -0,0 +1,16 @@
|
||||
from module.base.button import Button
|
||||
from module.base.template import Template
|
||||
|
||||
# This file was automatically generated by dev_tools/button_extract.py.
|
||||
# Don't modify it manually.
|
||||
|
||||
AWAKENING = Button(area={'cn': (803, 282, 893, 304), 'en': (803, 282, 893, 304), 'jp': (803, 282, 893, 304), 'tw': (803, 282, 893, 304)}, color={'cn': (183, 163, 158), 'en': (183, 163, 158), 'jp': (183, 163, 158), 'tw': (183, 163, 158)}, button={'cn': (803, 282, 893, 304), 'en': (803, 282, 893, 304), 'jp': (803, 282, 893, 304), 'tw': (803, 282, 893, 304)}, file={'cn': './assets/cn/awaken/AWAKENING.png', 'en': './assets/cn/awaken/AWAKENING.png', 'jp': './assets/cn/awaken/AWAKENING.png', 'tw': './assets/cn/awaken/AWAKENING.png'})
|
||||
AWAKEN_CANCEL = Button(area={'cn': (485, 514, 553, 543), 'en': (485, 514, 553, 543), 'jp': (485, 514, 553, 543), 'tw': (485, 514, 553, 543)}, color={'cn': (214, 160, 154), 'en': (214, 160, 154), 'jp': (214, 160, 154), 'tw': (214, 160, 154)}, button={'cn': (485, 514, 553, 543), 'en': (485, 514, 553, 543), 'jp': (485, 514, 553, 543), 'tw': (485, 514, 553, 543)}, file={'cn': './assets/cn/awaken/AWAKEN_CANCEL.png', 'en': './assets/cn/awaken/AWAKEN_CANCEL.png', 'jp': './assets/cn/awaken/AWAKEN_CANCEL.png', 'tw': './assets/cn/awaken/AWAKEN_CANCEL.png'})
|
||||
AWAKEN_CONFIRM = Button(area={'cn': (727, 513, 795, 542), 'en': (727, 513, 795, 542), 'jp': (727, 513, 795, 542), 'tw': (727, 513, 795, 542)}, color={'cn': (151, 182, 222), 'en': (151, 182, 222), 'jp': (151, 182, 222), 'tw': (151, 182, 222)}, button={'cn': (727, 513, 795, 542), 'en': (727, 513, 795, 542), 'jp': (727, 513, 795, 542), 'tw': (727, 513, 795, 542)}, file={'cn': './assets/cn/awaken/AWAKEN_CONFIRM.png', 'en': './assets/cn/awaken/AWAKEN_CONFIRM.png', 'jp': './assets/cn/awaken/AWAKEN_CONFIRM.png', 'tw': './assets/cn/awaken/AWAKEN_CONFIRM.png'})
|
||||
AWAKEN_FINISH = Button(area={'cn': (435, 343, 468, 382), 'en': (435, 343, 468, 382), 'jp': (435, 343, 468, 382), 'tw': (435, 343, 468, 382)}, color={'cn': (58, 152, 193), 'en': (58, 152, 193), 'jp': (58, 152, 193), 'tw': (58, 152, 193)}, button={'cn': (218, 561, 548, 630), 'en': (218, 561, 548, 630), 'jp': (218, 561, 548, 630), 'tw': (218, 561, 548, 630)}, file={'cn': './assets/cn/awaken/AWAKEN_FINISH.png', 'en': './assets/cn/awaken/AWAKEN_FINISH.png', 'jp': './assets/cn/awaken/AWAKEN_FINISH.png', 'tw': './assets/cn/awaken/AWAKEN_FINISH.png'})
|
||||
COST_ARRAY = Button(area={'cn': (720, 374, 780, 434), 'en': (720, 374, 780, 434), 'jp': (720, 374, 780, 434), 'tw': (720, 374, 780, 434)}, color={'cn': (141, 163, 178), 'en': (141, 163, 178), 'jp': (141, 163, 178), 'tw': (141, 163, 178)}, button={'cn': (720, 374, 780, 434), 'en': (720, 374, 780, 434), 'jp': (720, 374, 780, 434), 'tw': (720, 374, 780, 434)}, file={'cn': './assets/cn/awaken/COST_ARRAY.png', 'en': './assets/cn/awaken/COST_ARRAY.png', 'jp': './assets/cn/awaken/COST_ARRAY.png', 'tw': './assets/cn/awaken/COST_ARRAY.png'})
|
||||
COST_CHIP = Button(area={'cn': (610, 375, 670, 435), 'en': (610, 375, 670, 435), 'jp': (610, 375, 670, 435), 'tw': (610, 375, 670, 435)}, color={'cn': (157, 188, 190), 'en': (157, 188, 190), 'jp': (157, 188, 190), 'tw': (157, 188, 190)}, button={'cn': (610, 375, 670, 435), 'en': (610, 375, 670, 435), 'jp': (610, 375, 670, 435), 'tw': (610, 375, 670, 435)}, file={'cn': './assets/cn/awaken/COST_CHIP.png', 'en': './assets/cn/awaken/COST_CHIP.png', 'jp': './assets/cn/awaken/COST_CHIP.png', 'tw': './assets/cn/awaken/COST_CHIP.png'})
|
||||
COST_COIN = Button(area={'cn': (499, 373, 559, 433), 'en': (499, 373, 559, 433), 'jp': (499, 373, 559, 433), 'tw': (499, 373, 559, 433)}, color={'cn': (219, 180, 83), 'en': (219, 180, 83), 'jp': (219, 180, 83), 'tw': (219, 180, 83)}, button={'cn': (499, 373, 559, 433), 'en': (499, 373, 559, 433), 'jp': (499, 373, 559, 433), 'tw': (499, 373, 559, 433)}, file={'cn': './assets/cn/awaken/COST_COIN.png', 'en': './assets/cn/awaken/COST_COIN.png', 'jp': './assets/cn/awaken/COST_COIN.png', 'tw': './assets/cn/awaken/COST_COIN.png'})
|
||||
LEVEL_UP = Button(area={'cn': (804, 282, 897, 304), 'en': (804, 282, 897, 304), 'jp': (804, 282, 897, 304), 'tw': (804, 282, 897, 304)}, color={'cn': (141, 167, 216), 'en': (141, 167, 216), 'jp': (141, 167, 216), 'tw': (141, 167, 216)}, button={'cn': (804, 282, 897, 304), 'en': (804, 282, 897, 304), 'jp': (804, 282, 897, 304), 'tw': (804, 282, 897, 304)}, file={'cn': './assets/cn/awaken/LEVEL_UP.png', 'en': './assets/cn/awaken/LEVEL_UP.png', 'jp': './assets/cn/awaken/LEVEL_UP.png', 'tw': './assets/cn/awaken/LEVEL_UP.png'})
|
||||
OCR_SHIP_LEVEL = Button(area={'cn': (757, 283, 799, 319), 'en': (757, 283, 799, 319), 'jp': (757, 283, 799, 319), 'tw': (757, 283, 799, 319)}, color={'cn': (115, 130, 142), 'en': (115, 130, 142), 'jp': (115, 130, 142), 'tw': (115, 130, 142)}, button={'cn': (757, 283, 799, 319), 'en': (757, 283, 799, 319), 'jp': (757, 283, 799, 319), 'tw': (757, 283, 799, 319)}, file={'cn': './assets/cn/awaken/OCR_SHIP_LEVEL.png', 'en': './assets/cn/awaken/OCR_SHIP_LEVEL.png', 'jp': './assets/cn/awaken/OCR_SHIP_LEVEL.png', 'tw': './assets/cn/awaken/OCR_SHIP_LEVEL.png'})
|
||||
SHIP_LEVEL_CHECK = Button(area={'cn': (694, 287, 748, 316), 'en': (694, 287, 748, 316), 'jp': (694, 287, 748, 316), 'tw': (694, 287, 748, 316)}, color={'cn': (151, 140, 179), 'en': (151, 140, 179), 'jp': (151, 140, 179), 'tw': (151, 140, 179)}, button={'cn': (694, 287, 748, 316), 'en': (694, 287, 748, 316), 'jp': (694, 287, 748, 316), 'tw': (694, 287, 748, 316)}, file={'cn': './assets/cn/awaken/SHIP_LEVEL_CHECK.png', 'en': './assets/cn/awaken/SHIP_LEVEL_CHECK.png', 'jp': './assets/cn/awaken/SHIP_LEVEL_CHECK.png', 'tw': './assets/cn/awaken/SHIP_LEVEL_CHECK.png'})
|
361
module/awaken/awaken.py
Normal file
@ -0,0 +1,361 @@
|
||||
from module.awaken.assets import *
|
||||
from module.base.timer import Timer
|
||||
from module.exception import ScriptError
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import Digit
|
||||
from module.retire.dock import CARD_GRIDS, DOCK_EMPTY, Dock, SHIP_DETAIL_CHECK
|
||||
from module.ui.assets import BACK_ARROW
|
||||
from module.ui.page import page_dock, page_main
|
||||
|
||||
|
||||
class ShipLevel(Digit):
|
||||
def after_process(self, result):
|
||||
result = super().after_process(result)
|
||||
if result < 100 or result > 125:
|
||||
logger.warning('Unexpected ship level')
|
||||
result = 0
|
||||
return result
|
||||
|
||||
|
||||
class Awaken(Dock):
|
||||
def _get_button_state(self, button: Button):
|
||||
"""
|
||||
Args:
|
||||
button: COST_COIN or COST_CHIP or COST_ARRAY
|
||||
|
||||
Returns:
|
||||
bool: True if having sufficient resource, False if not
|
||||
or None if such resource is not required
|
||||
"""
|
||||
# If COST_ARRAY is absent, COST_COIN and COST_CHIP are right moved 54px
|
||||
if button.match(self.device.image, offset=(75, 20)):
|
||||
# Look down, see if there are red letters
|
||||
area = button.button
|
||||
area = (area[0], area[3], area[2], area[3] + 60)
|
||||
if self.image_color_count(area, color=(214, 53, 33), threshold=180, count=16):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_awaken_cost(self, use_array=False):
|
||||
"""
|
||||
Args:
|
||||
use_array: True to awaken to 125, False to 120
|
||||
|
||||
Returns:
|
||||
bool or str:
|
||||
True if all required resource is sufficient,
|
||||
False if any is insufficient,
|
||||
'unexpected_array' if not going to use array but array presents,
|
||||
'invalid' if result valid,
|
||||
"""
|
||||
coin = self._get_button_state(COST_COIN)
|
||||
chip = self._get_button_state(COST_CHIP)
|
||||
array = self._get_button_state(COST_ARRAY)
|
||||
|
||||
logger.attr('AwaikenCost', {'coin': coin, 'chip': chip, 'array': array})
|
||||
|
||||
def is_right_moved(button):
|
||||
# If COST_ARRAY is absent, COST_COIN and COST_CHIP are right moved 54px
|
||||
return button.button[0] - button.area[0] > 20
|
||||
|
||||
# Check if result are valid
|
||||
if array is not None:
|
||||
if not use_array:
|
||||
logger.warning('Not going to use array but array presents')
|
||||
return 'unexpected_array'
|
||||
# If array is needed, coin and chip should present
|
||||
if coin is not None and not is_right_moved(COST_COIN) \
|
||||
and chip is not None and not is_right_moved(COST_CHIP):
|
||||
result = coin and chip and array
|
||||
logger.attr('AwaikenSufficient', result)
|
||||
return result
|
||||
else:
|
||||
# If array is not needed, coin and chip should both present and right moved
|
||||
if coin is not None and is_right_moved(COST_COIN) \
|
||||
and chip is not None and is_right_moved(COST_CHIP):
|
||||
result = coin and chip
|
||||
logger.attr('AwaikenSufficient', result)
|
||||
return result
|
||||
|
||||
logger.warning('Invalid awaken cost')
|
||||
return 'invalid'
|
||||
|
||||
def handle_awaken_finish(self):
|
||||
return self.appear_then_click(AWAKEN_FINISH, offset=(20, 20), interval=1)
|
||||
|
||||
def is_in_awaken(self):
|
||||
return SHIP_LEVEL_CHECK.match_luma(self.device.image)
|
||||
|
||||
def awaken_popup_close(self, skip_first_screenshot=True):
|
||||
logger.info('Awaken popup close')
|
||||
self.interval_clear(AWAKEN_CANCEL)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.is_in_awaken():
|
||||
break
|
||||
if self.appear_then_click(AWAKEN_CANCEL, offset=(20, 20), interval=3):
|
||||
continue
|
||||
if self.handle_awaken_finish():
|
||||
continue
|
||||
|
||||
def awaken_once(self, use_array=False, skip_first_screenshot=True):
|
||||
"""
|
||||
Args:
|
||||
use_array:
|
||||
skip_first_screenshot:
|
||||
|
||||
Returns:
|
||||
str: Result state, 'no_exp', 'unexpected_array', 'insufficient', 'timeout', 'success'
|
||||
|
||||
Pages:
|
||||
in: is_in_awaken
|
||||
out: is_in_awaken
|
||||
"""
|
||||
logger.hr('Awaken once', level=2)
|
||||
interval = Timer(3, count=6)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.appear(AWAKEN_CONFIRM):
|
||||
break
|
||||
if LEVEL_UP.match_luma(self.device.image):
|
||||
logger.info(f'awaken_once ended at {LEVEL_UP}')
|
||||
return 'no_exp'
|
||||
if interval.reached() and AWAKENING.match_luma(self.device.image):
|
||||
self.device.click(AWAKENING)
|
||||
interval.reset()
|
||||
continue
|
||||
|
||||
logger.info('Get awaken cost')
|
||||
timeout = Timer(2, count=6).start()
|
||||
skip_first_screenshot = True
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
result = self._get_awaken_cost(use_array)
|
||||
if result == 'unexpected_array':
|
||||
# Get resources time out, assume sufficient
|
||||
self.awaken_popup_close()
|
||||
return 'unexpected_array'
|
||||
elif result is False:
|
||||
logger.info('Insufficient resources to awaken')
|
||||
return 'insufficient'
|
||||
elif result is True:
|
||||
break
|
||||
elif result == 'invalid':
|
||||
# Retry, and check timeout also
|
||||
pass
|
||||
else:
|
||||
raise ScriptError(f'Unexpected _get_awaken_cost result: {result}')
|
||||
if timeout.reached():
|
||||
logger.warning('Get awaken cost timeout')
|
||||
self.awaken_popup_close()
|
||||
return 'timeout'
|
||||
|
||||
# sufficient is True
|
||||
logger.info('Awaken confirm')
|
||||
self.interval_clear(AWAKEN_CONFIRM)
|
||||
# Awaken popup takes 10s to appear if you have enough EXP to reach next awaken limit
|
||||
# and 2s to dismiss it by clicking
|
||||
# Timeout here is very long
|
||||
timeout = Timer(30, count=30).start()
|
||||
finished = False
|
||||
skip_first_screenshot = True
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# End
|
||||
if timeout.reached():
|
||||
logger.warning('Awaken confirm timeout')
|
||||
self.awaken_popup_close()
|
||||
break
|
||||
if finished and self.is_in_awaken():
|
||||
logger.info('Awaken finished')
|
||||
break
|
||||
# Click
|
||||
if self.appear_then_click(AWAKEN_CONFIRM, offset=(20, 20), interval=3):
|
||||
continue
|
||||
if self.handle_popup_confirm('AWAKEN'):
|
||||
continue
|
||||
if self.handle_awaken_finish():
|
||||
finished = True
|
||||
continue
|
||||
|
||||
self.device.click_record_clear()
|
||||
return 'success'
|
||||
|
||||
def get_ship_level(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Args:
|
||||
skip_first_screenshot:
|
||||
|
||||
Returns:
|
||||
int: 100~125, or 0 if error
|
||||
"""
|
||||
ocr = ShipLevel(OCR_SHIP_LEVEL, letter=(255, 255, 255), threshold=128, name='ShipLevel')
|
||||
timeout = Timer(2, count=4).start()
|
||||
level = 0
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.is_in_awaken():
|
||||
level = ocr.ocr(self.device.image)
|
||||
if level > 0:
|
||||
return level
|
||||
if timeout.reached():
|
||||
logger.warning('get_ship_level timeout')
|
||||
return level
|
||||
|
||||
def awaken_ship(self, use_array=False, skip_first_screenshot=True):
|
||||
"""
|
||||
Awaken one ship til EXP not enough or reached stop level
|
||||
|
||||
Args:
|
||||
use_array: True to awaken to level 125, False to 120
|
||||
skip_first_screenshot:
|
||||
|
||||
Returns:
|
||||
str: 'level_max', 'insufficient', 'no_exp', 'timeout'
|
||||
|
||||
Pages:
|
||||
in: is_in_awaken
|
||||
out: is_in_awaken
|
||||
"""
|
||||
logger.hr('Awaken ship', level=1)
|
||||
logger.info(f'Awaken ship, use_array={use_array}')
|
||||
|
||||
if use_array:
|
||||
stop_level = 125
|
||||
else:
|
||||
stop_level = 120
|
||||
|
||||
if not skip_first_screenshot:
|
||||
self.device.screenshot()
|
||||
|
||||
for _ in range(7):
|
||||
level = self.get_ship_level()
|
||||
if level > 0:
|
||||
if level >= stop_level:
|
||||
logger.info(f'Awaken ship ended at stop_level')
|
||||
return 'level_max'
|
||||
else:
|
||||
result = self.awaken_once(use_array)
|
||||
# 'no_exp', 'unexpected_array', 'insufficient', 'timeout', 'success'
|
||||
if result == 'success':
|
||||
continue
|
||||
if result in ['insufficient', 'no_exp']:
|
||||
# Return as it is
|
||||
return result
|
||||
if result == 'unexpected_array':
|
||||
# Maybe just accidentally entered awaken confirm
|
||||
# Re-run awaken_once should recheck it
|
||||
continue
|
||||
if result == 'timeout':
|
||||
# Timeout getting resources, retry should fix it
|
||||
continue
|
||||
raise ScriptError(f'Unexpected awaken_once result: {result}')
|
||||
else:
|
||||
# Get level timeout, request exit
|
||||
return 'timeout'
|
||||
|
||||
# Error, request exit
|
||||
logger.warning('Too many awaken trial on one ship')
|
||||
return 'timeout'
|
||||
|
||||
def awaken_exit(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Pages:
|
||||
in: is_in_awaken
|
||||
out: DOCK_CHECK
|
||||
"""
|
||||
logger.info('Awaken exit')
|
||||
interval = Timer(3)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.ui_page_appear(page_dock):
|
||||
logger.info(f'Awaken exit at {page_dock}')
|
||||
break
|
||||
if interval.reached() and self.is_in_awaken():
|
||||
logger.info(f'is_in_awaken -> {BACK_ARROW}')
|
||||
self.device.click(BACK_ARROW)
|
||||
interval.reset()
|
||||
continue
|
||||
if self.handle_awaken_finish():
|
||||
continue
|
||||
if self.is_in_main(interval=5):
|
||||
self.device.click(page_main.links[page_dock])
|
||||
continue
|
||||
|
||||
def awaken_run(self, use_array=False):
|
||||
"""
|
||||
Awaken all ships in dock until resources exhausted
|
||||
|
||||
Args:
|
||||
use_array: True to awaken to level 125, False to 120
|
||||
|
||||
Returns:
|
||||
str: 'insufficient', 'timeout'
|
||||
|
||||
Pages:
|
||||
in: Any
|
||||
out: page_dock
|
||||
"""
|
||||
self.ui_ensure(page_dock)
|
||||
self.dock_favourite_set(wait_loading=False)
|
||||
self.dock_sort_method_dsc_set(wait_loading=False)
|
||||
if use_array:
|
||||
extra = ['can_awaken', 'can_awaken_plus']
|
||||
else:
|
||||
extra = ['can_awaken']
|
||||
self.dock_filter_set(extra=extra)
|
||||
|
||||
while 1:
|
||||
# page_dock
|
||||
if self.appear(DOCK_EMPTY, offset=(20, 20)):
|
||||
logger.info('awaken_run finished, no ships to awaken')
|
||||
break
|
||||
|
||||
# page_dock -> SHIP_DETAIL_CHECK
|
||||
self.ship_info_enter(
|
||||
CARD_GRIDS[(0, 0)], check_button=SHIP_DETAIL_CHECK, long_click=False)
|
||||
|
||||
# is_in_awaken
|
||||
result = self.awaken_ship(use_array)
|
||||
self.awaken_exit()
|
||||
# 'insufficient', 'no_exp', 'timeout'
|
||||
if result in ['no_exp', 'level_max']:
|
||||
# Awaken next ship
|
||||
continue
|
||||
if result == 'insufficient':
|
||||
logger.info('awaken_run finished, resources exhausted')
|
||||
return result
|
||||
if result == 'timeout':
|
||||
logger.info(f'awaken_run finished, result={result}')
|
||||
return result
|
||||
raise ScriptError(f'Unexpected awaken_ship result: {result}')
|
||||
|
||||
logger.hr('Awaken run exit', level=1)
|
||||
self.dock_filter_set()
|