Add: UI switches and login

This commit is contained in:
LmeSzinc 2023-05-18 00:44:43 +08:00
parent a0ee78cdf3
commit 82d013ed59
38 changed files with 682 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -44,7 +44,7 @@ class ModuleBase:
self.interval_timer = {} self.interval_timer = {}
def match_template(self, button, interval=5, similarity=0.85): def match_template(self, button, interval=0, similarity=0.85):
""" """
Args: Args:
button (ButtonWrapper): button (ButtonWrapper):
@ -74,7 +74,7 @@ class ModuleBase:
return appear return appear
def match_color(self, button, interval=5, threshold=10): def match_color(self, button, interval=0, threshold=10):
""" """
Args: Args:
button (ButtonWrapper): button (ButtonWrapper):
@ -96,7 +96,7 @@ class ModuleBase:
return appear return appear
def match_template_color(self, button, interval=5, similarity=0.85, threshold=30): def match_template_color(self, button, interval=0, similarity=0.85, threshold=30):
""" """
Args: Args:
button (ButtonWrapper): button (ButtonWrapper):

View File

@ -0,0 +1,245 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
BATTLE_PASS_CHECK = ButtonWrapper(
name='BATTLE_PASS_CHECK',
share=Button(
file='./assets/share/base/page/BATTLE_PASS_CHECK.png',
area=(42, 22, 72, 55),
search=(22, 2, 92, 75),
color=(159, 142, 108),
button=(42, 22, 72, 55),
),
)
CHARACTER_CHECK = ButtonWrapper(
name='CHARACTER_CHECK',
share=Button(
file='./assets/share/base/page/CHARACTER_CHECK.png',
area=(41, 18, 73, 47),
search=(21, 0, 93, 67),
color=(172, 153, 119),
button=(41, 18, 73, 47),
),
)
CLOSE = ButtonWrapper(
name='CLOSE',
share=Button(
file='./assets/share/base/page/CLOSE.png',
area=(1222, 25, 1252, 55),
search=(1202, 5, 1272, 75),
color=(53, 54, 54),
button=(1222, 25, 1252, 55),
),
)
EVENT_CHECK = ButtonWrapper(
name='EVENT_CHECK',
share=Button(
file='./assets/share/base/page/EVENT_CHECK.png',
area=(39, 19, 75, 56),
search=(19, 0, 95, 76),
color=(133, 125, 103),
button=(39, 19, 75, 56),
),
)
GACHA_CHECK = ButtonWrapper(
name='GACHA_CHECK',
share=Button(
file='./assets/share/base/page/GACHA_CHECK.png',
area=(40, 20, 74, 54),
search=(20, 0, 94, 74),
color=(157, 139, 112),
button=(40, 20, 74, 54),
),
)
GUIDE_CHECK = ButtonWrapper(
name='GUIDE_CHECK',
share=Button(
file='./assets/share/base/page/GUIDE_CHECK.png',
area=(44, 216, 63, 248),
search=(24, 196, 83, 268),
color=(196, 198, 200),
button=(44, 216, 63, 248),
),
)
GUIDE_CLOSE = ButtonWrapper(
name='GUIDE_CLOSE',
share=Button(
file='./assets/share/base/page/GUIDE_CLOSE.png',
area=(1153, 56, 1183, 87),
search=(1133, 36, 1203, 107),
color=(79, 79, 79),
button=(1153, 56, 1183, 87),
),
)
ITEM_CHECK = ButtonWrapper(
name='ITEM_CHECK',
share=Button(
file='./assets/share/base/page/ITEM_CHECK.png',
area=(43, 23, 72, 54),
search=(23, 3, 92, 74),
color=(188, 169, 129),
button=(43, 23, 72, 54),
),
)
MAIN_GOTO_BATTLE_PASS = ButtonWrapper(
name='MAIN_GOTO_BATTLE_PASS',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_BATTLE_PASS.png',
area=(860, 36, 889, 56),
search=(840, 16, 909, 76),
color=(165, 164, 162),
button=(860, 36, 889, 56),
),
)
MAIN_GOTO_CHARACTER = ButtonWrapper(
name='MAIN_GOTO_CHARACTER',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_CHARACTER.png',
area=(1204, 25, 1234, 51),
search=(1184, 5, 1254, 71),
color=(184, 185, 187),
button=(1204, 25, 1234, 51),
),
)
MAIN_GOTO_EVENT = ButtonWrapper(
name='MAIN_GOTO_EVENT',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_EVENT.png',
area=(786, 33, 814, 56),
search=(766, 13, 834, 76),
color=(185, 184, 183),
button=(786, 33, 814, 56),
),
)
MAIN_GOTO_GACHA = ButtonWrapper(
name='MAIN_GOTO_GACHA',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_GACHA.png',
area=(929, 38, 957, 59),
search=(909, 18, 977, 79),
color=(161, 162, 163),
button=(929, 38, 957, 59),
),
)
MAIN_GOTO_GUIDE = ButtonWrapper(
name='MAIN_GOTO_GUIDE',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_GUIDE.png',
area=(997, 34, 1027, 59),
search=(977, 14, 1047, 79),
color=(170, 171, 173),
button=(997, 34, 1027, 59),
),
)
MAIN_GOTO_ITEM = ButtonWrapper(
name='MAIN_GOTO_ITEM',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_ITEM.png',
area=(1064, 35, 1098, 59),
search=(1044, 15, 1118, 79),
color=(179, 180, 182),
button=(1064, 35, 1098, 59),
),
)
MAIN_GOTO_MENU = ButtonWrapper(
name='MAIN_GOTO_MENU',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_MENU.png',
area=(22, 60, 51, 81),
search=(2, 40, 71, 101),
color=(176, 177, 179),
button=(22, 60, 51, 81),
),
)
MAIN_GOTO_MESSAGE = ButtonWrapper(
name='MAIN_GOTO_MESSAGE',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_MESSAGE.png',
area=(184, 182, 203, 212),
search=(164, 162, 223, 232),
color=(230, 230, 230),
button=(184, 182, 203, 212),
),
)
MAIN_GOTO_MISSION = ButtonWrapper(
name='MAIN_GOTO_MISSION',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_MISSION.png',
area=(21, 199, 51, 221),
search=(1, 179, 71, 241),
color=(168, 169, 172),
button=(21, 199, 51, 221),
),
)
MAIN_GOTO_TEAM = ButtonWrapper(
name='MAIN_GOTO_TEAM',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_TEAM.png',
area=(1135, 41, 1166, 58),
search=(1115, 21, 1186, 78),
color=(160, 161, 164),
button=(1135, 41, 1166, 58),
),
)
MAIN_GOTO_TUTORIAL = ButtonWrapper(
name='MAIN_GOTO_TUTORIAL',
share=Button(
file='./assets/share/base/page/MAIN_GOTO_TUTORIAL.png',
area=(195, 58, 207, 82),
search=(175, 38, 227, 102),
color=(127, 131, 139),
button=(195, 58, 207, 82),
),
)
MENU_CHECK = ButtonWrapper(
name='MENU_CHECK',
share=Button(
file='./assets/share/base/page/MENU_CHECK.png',
area=(1222, 638, 1252, 669),
search=(1202, 618, 1272, 689),
color=(57, 50, 39),
button=(1222, 638, 1252, 669),
),
)
MESSAGE_CLOSE = ButtonWrapper(
name='MESSAGE_CLOSE',
share=Button(
file='./assets/share/base/page/MESSAGE_CLOSE.png',
area=(863, 95, 895, 127),
search=(843, 75, 915, 147),
color=(175, 174, 175),
button=(863, 95, 895, 127),
),
)
MISSION_CHECK = ButtonWrapper(
name='MISSION_CHECK',
share=Button(
file='./assets/share/base/page/MISSION_CHECK.png',
area=(44, 33, 70, 55),
search=(24, 13, 90, 75),
color=(194, 177, 139),
button=(44, 33, 70, 55),
),
)
TEAM_CHECK = ButtonWrapper(
name='TEAM_CHECK',
share=Button(
file='./assets/share/base/page/TEAM_CHECK.png',
area=(41, 34, 73, 54),
search=(21, 14, 93, 74),
color=(138, 123, 101),
button=(41, 34, 73, 54),
),
)
TUTORIAL_CHECK = ButtonWrapper(
name='TUTORIAL_CHECK',
share=Button(
file='./assets/share/base/page/TUTORIAL_CHECK.png',
area=(44, 30, 70, 56),
search=(24, 10, 90, 76),
color=(141, 126, 99),
button=(44, 30, 70, 56),
),
)

View File

@ -0,0 +1,25 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
BATTLE_PASS_NOTIFICATION = ButtonWrapper(
name='BATTLE_PASS_NOTIFICATION',
share=Button(
file='./assets/share/base/popup/BATTLE_PASS_NOTIFICATION.png',
area=(960, 601, 984, 625),
search=(940, 581, 1004, 645),
color=(137, 127, 109),
button=(895, 595, 1180, 630),
),
)
GET_REWARD = ButtonWrapper(
name='GET_REWARD',
share=Button(
file='./assets/share/base/popup/GET_REWARD.png',
area=(625, 118, 655, 147),
search=(605, 98, 675, 167),
color=(167, 151, 116),
button=(741, 495, 1071, 644),
),
)

122
tasks/base/page.py Normal file
View File

@ -0,0 +1,122 @@
import traceback
from tasks.base.assets.assets_base_page import *
class Page:
# Key: str, page name like "page_main"
# Value: Page, page instance
all_pages = {}
@classmethod
def clear_connection(cls):
for page in cls.all_pages.values():
page.parent = None
@classmethod
def init_connection(cls, destination):
"""
Initialize an A* path finding among pages.
Args:
destination (Page):
"""
cls.clear_connection()
visited = [destination]
visited = set(visited)
while 1:
new = visited.copy()
for page in visited:
for link in cls.iter_pages():
if link in visited:
continue
if page in link.links:
link.parent = page
new.add(link)
if len(new) == len(visited):
break
visited = new
@classmethod
def iter_pages(cls):
return cls.all_pages.values()
@classmethod
def iter_check_buttons(cls):
for page in cls.all_pages.values():
yield page.check_button
def __init__(self, check_button):
self.check_button = check_button
self.links = {}
(filename, line_number, function_name, text) = traceback.extract_stack()[-2]
self.name = text[:text.find('=')].strip()
self.parent = None
Page.all_pages[self.name] = self
def __eq__(self, other):
return self.name == other.name
def __hash__(self):
return hash(self.name)
def __str__(self):
return self.name
def link(self, button, destination):
self.links[destination] = button
# Main page
page_main = Page(MAIN_GOTO_CHARACTER)
# Menu, entered from phone
page_menu = Page(MENU_CHECK)
page_menu.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_MENU, destination=page_menu)
# Team
page_team = Page(TEAM_CHECK)
page_team.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_TEAM, destination=page_team)
# Item, storage
page_item = Page(ITEM_CHECK)
page_item.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_ITEM, destination=page_item)
# Guide, which includes beginners' guide, daily missions and dungeons
page_guide = Page(GUIDE_CHECK)
page_guide.link(GUIDE_CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_GUIDE, destination=page_guide)
# Gacha
page_gacha = Page(GACHA_CHECK)
page_gacha.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_GACHA, destination=page_gacha)
# Battle Pass
page_battle_pass = Page(BATTLE_PASS_CHECK)
page_battle_pass.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_BATTLE_PASS, destination=page_battle_pass)
# Event
page_event = Page(EVENT_CHECK)
page_event.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_EVENT, destination=page_event)
# Tutorial
page_tutorial = Page(TUTORIAL_CHECK)
page_tutorial.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_TUTORIAL, destination=page_tutorial)
# Mission
page_mission = Page(MISSION_CHECK)
page_mission.link(CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_MISSION, destination=page_mission)
# Message
page_message = Page(MESSAGE_CLOSE)
page_message.link(MESSAGE_CLOSE, destination=page_main)
page_main.link(MAIN_GOTO_MESSAGE, destination=page_message)

32
tasks/base/popup.py Normal file
View File

@ -0,0 +1,32 @@
from module.base.base import ModuleBase
from tasks.base.assets.assets_base_popup import *
class PopupHandler(ModuleBase):
def handle_reward(self, interval=5) -> bool:
"""
Args:
interval:
Returns:
If handled.
"""
if self.appear_then_click(GET_REWARD, interval=interval):
return True
return False
def handle_battle_pass_notification(self, interval=5) -> bool:
"""
Popup notification that you enter battle pass the first time.
Args:
interval:
Returns:
If handled.
"""
if self.appear_then_click(BATTLE_PASS_NOTIFICATION, interval=interval):
return True
return False

175
tasks/base/ui.py Normal file
View File

@ -0,0 +1,175 @@
from module.base.decorator import run_once
from module.base.timer import Timer
from module.exception import GameNotRunningError, GamePageUnknownError
from module.logger import logger
from tasks.base.page import Page, page_main
from tasks.base.popup import PopupHandler
class UI(PopupHandler):
ui_current: Page
def ui_page_appear(self, page):
"""
Args:
page (Page):
"""
return self.appear(page.check_button)
def ui_get_current_page(self, skip_first_screenshot=True):
"""
Args:
skip_first_screenshot:
Returns:
Page:
Raises:
GameNotRunningError:
GamePageUnknownError:
"""
logger.info("UI get current page")
@run_once
def app_check():
if not self.device.app_is_running():
raise GameNotRunningError("Game not running")
@run_once
def minicap_check():
if self.config.Emulator_ControlMethod == "uiautomator2":
self.device.uninstall_minicap()
@run_once
def rotation_check():
self.device.get_orientation()
timeout = Timer(10, count=20).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
if not hasattr(self.device, "image") or self.device.image is None:
self.device.screenshot()
else:
self.device.screenshot()
# End
if timeout.reached():
break
# Known pages
for page in Page.iter_pages():
if page.check_button is None:
continue
if self.ui_page_appear(page=page):
logger.attr("UI", page.name)
self.ui_current = page
return page
# Unknown page but able to handle
logger.info("Unknown ui page")
if self.ui_additional():
timeout.reset()
continue
app_check()
minicap_check()
rotation_check()
# Unknown page, need manual switching
logger.warning("Unknown ui page")
logger.attr("EMULATOR__SCREENSHOT_METHOD", self.config.Emulator_ScreenshotMethod)
logger.attr("EMULATOR__CONTROL_METHOD", self.config.Emulator_ControlMethod)
logger.attr("SERVER", self.config.SERVER)
logger.warning("Starting from current page is not supported")
logger.warning(f"Supported page: {[str(page) for page in Page.iter_pages()]}")
logger.warning('Supported page: Any page with a "HOME" button on the upper-right')
logger.critical("Please switch to a supported page before starting SRC")
raise GamePageUnknownError
def ui_goto(self, destination, skip_first_screenshot=True):
"""
Args:
destination (Page):
skip_first_screenshot:
"""
# Create connection
Page.init_connection(destination)
self.interval_clear(list(Page.iter_check_buttons()))
logger.hr(f"UI goto {destination}")
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# Destination page
if self.ui_page_appear(destination):
logger.info(f'Page arrive: {destination}')
break
# Other pages
clicked = False
for page in Page.iter_pages():
if page.parent is None or page.check_button is None:
continue
if self.appear(page.check_button, interval=5):
logger.info(f'Page switch: {page} -> {page.parent}')
button = page.links[page.parent]
self.device.click(button)
self.ui_button_interval_reset(button)
clicked = True
break
if clicked:
continue
# Additional
if self.ui_additional():
continue
# Reset connection
Page.clear_connection()
def ui_ensure(self, destination, skip_first_screenshot=True):
"""
Args:
destination (Page):
skip_first_screenshot:
Returns:
bool: If UI switched.
"""
logger.hr("UI ensure")
self.ui_get_current_page(skip_first_screenshot=skip_first_screenshot)
if self.ui_current == destination:
logger.info("Already at %s" % destination)
return False
else:
logger.info("Goto %s" % destination)
self.ui_goto(destination, skip_first_screenshot=True)
return True
def ui_goto_main(self):
return self.ui_ensure(destination=page_main)
def ui_additional(self) -> bool:
"""
Handle all possible popups during UI switching.
Returns:
If handled any popup.
"""
if self.handle_reward():
return True
if self.handle_battle_pass_notification():
return True
def ui_button_interval_reset(self, button):
"""
Reset interval of some button to avoid mistaken clicks
Args:
button (Button):
"""
pass

View File

@ -0,0 +1,15 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
LOGIN_CONFIRM = ButtonWrapper(
name='LOGIN_CONFIRM',
share=Button(
file='./assets/share/login/LOGIN_CONFIRM.png',
area=(1188, 44, 1220, 74),
search=(1168, 24, 1240, 94),
color=(140, 124, 144),
button=(683, 327, 1143, 620),
),
)

65
tasks/login/login.py Normal file
View File

@ -0,0 +1,65 @@
from module.base.timer import Timer
from module.logger import logger
from tasks.base.page import page_main
from tasks.base.ui import UI
from tasks.login.assets.assets_login import LOGIN_CONFIRM
class Login(UI):
def _handle_app_login(self):
"""
Pages:
in: Any page
out: page_main
Raises:
GameStuckError:
GameTooManyClickError:
"""
logger.hr('App login')
orientation_timer = Timer(5)
login_success = False
while 1:
# Watch device rotation
if not login_success and orientation_timer.reached():
# Screen may rotate after starting an app
self.device.get_orientation()
orientation_timer.reset()
self.device.screenshot()
# End
if self.ui_page_appear(page_main):
logger.info('Login to main confirm')
break
# Login
if self.appear_then_click(LOGIN_CONFIRM):
continue
return True
def handle_app_login(self):
self.device.screenshot_interval_set(1.0)
try:
self._handle_app_login()
finally:
self.device.screenshot_interval_set()
def app_stop(self):
logger.hr('App stop')
self.device.app_stop()
def app_start(self):
logger.hr('App start')
self.device.app_start()
self.handle_app_login()
def app_restart(self):
logger.hr('App restart')
self.device.app_stop()
self.device.app_start()
self.handle_app_login()
self.config.task_delay(server_update=True)