Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: Task Awaken #4489

Merged
merged 12 commits into from
Jan 4, 2025
Prev Previous commit
Next Next commit
Add: Awaken ships
  • Loading branch information
LmeSzinc committed Jan 3, 2025
commit 47e833fd9ea4e0f92a56bfab1304622c4a0ba9db
Binary file added assets/cn/awaken/AWAKENING.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/AWAKEN_CANCEL.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/AWAKEN_CONFIRM.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/AWAKEN_FINISH.BUTTON.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/AWAKEN_FINISH.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/COST_ARRAY.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/COST_CHIP.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/COST_COIN.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/LEVEL_UP.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/OCR_SHIP_LEVEL.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/cn/awaken/SHIP_LEVEL_CHECK.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions module/awaken/assets.py
Original file line number Diff line number Diff line change
@@ -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 changes: 361 additions & 0 deletions module/awaken/awaken.py
Original file line number Diff line number Diff line change
@@ -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()