1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2026-06-09 20:04:52 +00:00

Upload code

This commit is contained in:
2023-11-01 15:33:35 +08:00
commit 6860f2eb72
415 changed files with 50990 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
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 ```
ACCOUNT_INFO_CHECK = ButtonWrapper(
name='ACCOUNT_INFO_CHECK',
jp=Button(
file='./assets/jp/base/page/ACCOUNT_INFO_CHECK.png',
area=(108, 11, 235, 36),
search=(88, 0, 255, 56),
color=(193, 201, 210),
button=(108, 11, 235, 36),
),
)
BACK = ButtonWrapper(
name='BACK',
jp=Button(
file='./assets/jp/base/page/BACK.png',
area=(34, 19, 81, 56),
search=(14, 0, 101, 76),
color=(93, 118, 164),
button=(34, 19, 81, 56),
),
)
BOUNTY_CHECK = ButtonWrapper(
name='BOUNTY_CHECK',
jp=Button(
file='./assets/jp/base/page/BOUNTY_CHECK.png',
area=(107, 10, 158, 37),
search=(87, 0, 178, 57),
color=(150, 164, 177),
button=(107, 10, 158, 37),
),
)
CAFE_CHECK = ButtonWrapper(
name='CAFE_CHECK',
jp=Button(
file='./assets/jp/base/page/CAFE_CHECK.png',
area=(264, 11, 337, 35),
search=(244, 0, 357, 55),
color=(188, 197, 205),
button=(264, 11, 337, 35),
),
)
CIRCLE_CHECK = ButtonWrapper(
name='CIRCLE_CHECK',
jp=Button(
file='./assets/jp/base/page/CIRCLE_CHECK.png',
area=(107, 11, 211, 36),
search=(87, 0, 231, 56),
color=(195, 203, 211),
button=(107, 11, 211, 36),
),
)
COMMISSIONS_CHECK = ButtonWrapper(
name='COMMISSIONS_CHECK',
jp=Button(
file='./assets/jp/base/page/COMMISSIONS_CHECK.png',
area=(107, 10, 158, 37),
search=(87, 0, 178, 57),
color=(146, 160, 174),
button=(107, 10, 158, 37),
),
)
CRAFTING_CHECK = ButtonWrapper(
name='CRAFTING_CHECK',
jp=Button(
file='./assets/jp/base/page/CRAFTING_CHECK.png',
area=(108, 10, 214, 37),
search=(88, 0, 234, 57),
color=(200, 208, 215),
button=(108, 10, 214, 37),
),
)
GACHA_CHECK = ButtonWrapper(
name='GACHA_CHECK',
jp=Button(
file='./assets/jp/base/page/GACHA_CHECK.png',
area=(159, 10, 210, 37),
search=(139, 0, 230, 57),
color=(135, 148, 164),
button=(159, 10, 210, 37),
),
)
HOME = ButtonWrapper(
name='HOME',
jp=Button(
file='./assets/jp/base/page/HOME.png',
area=(1218, 8, 1253, 41),
search=(1198, 0, 1273, 61),
color=(168, 182, 205),
button=(1218, 8, 1253, 41),
),
)
LOADING_CHECK = ButtonWrapper(
name='LOADING_CHECK',
jp=Button(
file='./assets/jp/base/page/LOADING_CHECK.png',
area=(1084, 659, 1120, 674),
search=(1064, 639, 1140, 694),
color=(173, 196, 219),
button=(1084, 659, 1120, 674),
),
)
MAIL_CHECK = ButtonWrapper(
name='MAIL_CHECK',
jp=Button(
file='./assets/jp/base/page/MAIL_CHECK.png',
area=(108, 9, 186, 37),
search=(88, 0, 206, 57),
color=(205, 212, 219),
button=(108, 9, 186, 37),
),
)
MAIN_GO_TO_CAFE = ButtonWrapper(
name='MAIN_GO_TO_CAFE',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_CAFE.png',
area=(81, 638, 102, 668),
search=(61, 618, 122, 688),
color=(156, 209, 233),
button=(81, 638, 102, 668),
),
)
MAIN_GO_TO_CIRCLE = ButtonWrapper(
name='MAIN_GO_TO_CIRCLE',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_CIRCLE.png',
area=(540, 631, 583, 660),
search=(520, 611, 603, 680),
color=(131, 204, 234),
button=(540, 631, 583, 660),
),
)
MAIN_GO_TO_CRAFTING = ButtonWrapper(
name='MAIN_GO_TO_CRAFTING',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_CRAFTING.png',
area=(665, 622, 693, 664),
search=(645, 602, 713, 684),
color=(192, 229, 241),
button=(665, 622, 693, 664),
),
)
MAIN_GO_TO_GACHA = ButtonWrapper(
name='MAIN_GO_TO_GACHA',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_GACHA.png',
area=(900, 623, 924, 670),
search=(880, 603, 944, 690),
color=(157, 219, 241),
button=(900, 623, 924, 670),
),
)
MAIN_GO_TO_MAIL = ButtonWrapper(
name='MAIN_GO_TO_MAIL',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_MAIL.png',
area=(1130, 29, 1156, 49),
search=(1110, 9, 1176, 69),
color=(94, 121, 166),
button=(1130, 29, 1156, 49),
),
)
MAIN_GO_TO_MOMOTALK = ButtonWrapper(
name='MAIN_GO_TO_MOMOTALK',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png',
area=(154, 134, 177, 158),
search=(134, 114, 197, 178),
color=(255, 219, 227),
button=(154, 134, 177, 158),
),
)
MAIN_GO_TO_PURCHASE = ButtonWrapper(
name='MAIN_GO_TO_PURCHASE',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_PURCHASE.png',
area=(148, 204, 183, 253),
search=(128, 184, 203, 273),
color=(172, 214, 239),
button=(148, 204, 183, 253),
),
)
MAIN_GO_TO_SCHEDULE = ButtonWrapper(
name='MAIN_GO_TO_SCHEDULE',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png',
area=(194, 638, 216, 672),
search=(174, 618, 236, 692),
color=(149, 194, 222),
button=(194, 638, 216, 672),
),
)
MAIN_GO_TO_SHOP = ButtonWrapper(
name='MAIN_GO_TO_SHOP',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_SHOP.png',
area=(773, 630, 816, 667),
search=(753, 610, 836, 687),
color=(146, 208, 235),
button=(773, 630, 816, 667),
),
)
MAIN_GO_TO_TASK = ButtonWrapper(
name='MAIN_GO_TO_TASK',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_TASK.png',
area=(52, 220, 78, 248),
search=(32, 200, 98, 268),
color=(226, 207, 203),
button=(52, 220, 78, 248),
),
)
MAIN_GO_TO_WORK = ButtonWrapper(
name='MAIN_GO_TO_WORK',
jp=Button(
file='./assets/jp/base/page/MAIN_GO_TO_WORK.png',
area=(1167, 605, 1241, 632),
search=(1147, 585, 1261, 652),
color=(135, 149, 169),
button=(1167, 605, 1241, 632),
),
)
MISSION_CHECK = ButtonWrapper(
name='MISSION_CHECK',
jp=Button(
file='./assets/jp/base/page/MISSION_CHECK.png',
area=(108, 12, 183, 36),
search=(88, 0, 203, 56),
color=(188, 197, 206),
button=(108, 12, 183, 36),
),
)
MOMOTALK_CHECK = ButtonWrapper(
name='MOMOTALK_CHECK',
jp=Button(
file='./assets/jp/base/page/MOMOTALK_CHECK.png',
area=(144, 107, 169, 130),
search=(124, 87, 189, 150),
color=(253, 211, 219),
button=(144, 107, 169, 130),
),
)
MOMOTALK_GO_TO_MAIN = ButtonWrapper(
name='MOMOTALK_GO_TO_MAIN',
jp=Button(
file='./assets/jp/base/page/MOMOTALK_GO_TO_MAIN.png',
area=(1108, 105, 1134, 131),
search=(1088, 85, 1154, 151),
color=(252, 182, 194),
button=(1108, 105, 1134, 131),
),
)
SCHEDULE_CHECK = ButtonWrapper(
name='SCHEDULE_CHECK',
jp=Button(
file='./assets/jp/base/page/SCHEDULE_CHECK.png',
area=(108, 12, 159, 36),
search=(88, 0, 179, 56),
color=(188, 197, 206),
button=(108, 12, 159, 36),
),
)
SCHOOL_EXCHANGE_CHECK = ButtonWrapper(
name='SCHOOL_EXCHANGE_CHECK',
jp=Button(
file='./assets/jp/base/page/SCHOOL_EXCHANGE_CHECK.png',
area=(107, 11, 158, 36),
search=(87, 0, 178, 56),
color=(134, 149, 164),
button=(107, 11, 158, 36),
),
)
SHOP_CHECK = ButtonWrapper(
name='SHOP_CHECK',
jp=Button(
file='./assets/jp/base/page/SHOP_CHECK.png',
area=(108, 10, 212, 36),
search=(88, 0, 232, 56),
color=(200, 208, 215),
button=(108, 10, 212, 36),
),
)
STORY_CHECK = ButtonWrapper(
name='STORY_CHECK',
jp=Button(
file='./assets/jp/base/page/STORY_CHECK.png',
area=(108, 11, 157, 36),
search=(88, 0, 177, 56),
color=(194, 203, 211),
button=(108, 11, 157, 36),
),
)
TACTICAL_CHALLENGE_CHECK = ButtonWrapper(
name='TACTICAL_CHALLENGE_CHECK',
jp=Button(
file='./assets/jp/base/page/TACTICAL_CHALLENGE_CHECK.png',
area=(107, 11, 133, 37),
search=(87, 0, 153, 57),
color=(129, 145, 161),
button=(107, 11, 133, 37),
),
)
TASK_CHECK = ButtonWrapper(
name='TASK_CHECK',
jp=Button(
file='./assets/jp/base/page/TASK_CHECK.png',
area=(109, 12, 155, 36),
search=(89, 0, 175, 56),
color=(189, 198, 207),
button=(109, 12, 155, 36),
),
)
WORK_CHECK = ButtonWrapper(
name='WORK_CHECK',
jp=Button(
file='./assets/jp/base/page/WORK_CHECK.png',
area=(107, 10, 185, 38),
search=(87, 0, 205, 58),
color=(164, 175, 187),
button=(107, 10, 185, 38),
),
)
WORK_GO_TO_BOUNTY = ButtonWrapper(
name='WORK_GO_TO_BOUNTY',
jp=Button(
file='./assets/jp/base/page/WORK_GO_TO_BOUNTY.png',
area=(669, 412, 765, 436),
search=(649, 392, 785, 456),
color=(165, 181, 208),
button=(669, 412, 765, 436),
),
)
WORK_GO_TO_COMMISSIONS = ButtonWrapper(
name='WORK_GO_TO_COMMISSIONS',
jp=Button(
file='./assets/jp/base/page/WORK_GO_TO_COMMISSIONS.png',
area=(655, 494, 751, 518),
search=(635, 474, 771, 538),
color=(165, 179, 204),
button=(655, 494, 751, 518),
),
)
WORK_GO_TO_MISSION = ButtonWrapper(
name='WORK_GO_TO_MISSION',
jp=Button(
file='./assets/jp/base/page/WORK_GO_TO_MISSION.png',
area=(720, 160, 803, 199),
search=(700, 140, 823, 219),
color=(165, 178, 204),
button=(720, 160, 803, 199),
),
)
WORK_GO_TO_SCHOOL_EXCHANGE = ButtonWrapper(
name='WORK_GO_TO_SCHOOL_EXCHANGE',
jp=Button(
file='./assets/jp/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png',
area=(641, 575, 758, 599),
search=(621, 555, 778, 619),
color=(165, 179, 204),
button=(641, 575, 758, 599),
),
)
WORK_GO_TO_STORY = ButtonWrapper(
name='WORK_GO_TO_STORY',
jp=Button(
file='./assets/jp/base/page/WORK_GO_TO_STORY.png',
area=(995, 163, 1032, 197),
search=(975, 143, 1052, 217),
color=(191, 201, 219),
button=(995, 163, 1032, 197),
),
)
WORK_GO_TO_TACTICAL_CHALLENGE = ButtonWrapper(
name='WORK_GO_TO_TACTICAL_CHALLENGE',
jp=Button(
file='./assets/jp/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png',
area=(1012, 535, 1151, 562),
search=(992, 515, 1171, 582),
color=(159, 174, 200),
button=(1012, 535, 1151, 562),
),
)
+75
View File
@@ -0,0 +1,75 @@
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 ```
AFFECTION_LEVEL_UP = ButtonWrapper(
name='AFFECTION_LEVEL_UP',
jp=Button(
file='./assets/jp/base/popup/AFFECTION_LEVEL_UP.png',
area=(643, 599, 773, 641),
search=(623, 579, 793, 661),
color=(208, 223, 243),
button=(882, 244, 1176, 476),
),
)
DAILY_NEWS = ButtonWrapper(
name='DAILY_NEWS',
jp=Button(
file='./assets/jp/base/popup/DAILY_NEWS.png',
area=(120, 89, 326, 113),
search=(100, 69, 346, 133),
color=(150, 204, 253),
button=(1128, 89, 1156, 117),
),
)
DAILY_REWARD = ButtonWrapper(
name='DAILY_REWARD',
jp=Button(
file='./assets/jp/base/popup/DAILY_REWARD.png',
area=(854, 117, 1008, 165),
search=(834, 97, 1028, 185),
color=(178, 167, 112),
button=(920, 632, 1140, 712),
),
)
GET_NEW_STUDENT = ButtonWrapper(
name='GET_NEW_STUDENT',
jp=Button(
file='./assets/jp/base/popup/GET_NEW_STUDENT.png',
area=(32, 93, 158, 114),
search=(12, 73, 178, 134),
color=(125, 132, 92),
button=(934, 643, 1263, 714),
),
)
GET_REWARD = ButtonWrapper(
name='GET_REWARD',
jp=Button(
file='./assets/jp/base/popup/GET_REWARD.png',
area=(657, 145, 693, 170),
search=(637, 125, 713, 190),
color=(230, 222, 93),
button=(675, 623, 870, 695),
),
)
GET_REWARD_SKIP = ButtonWrapper(
name='GET_REWARD_SKIP',
jp=Button(
file='./assets/jp/base/popup/GET_REWARD_SKIP.png',
area=(1137, 34, 1243, 65),
search=(1117, 14, 1263, 85),
color=(197, 200, 205),
button=(1137, 34, 1243, 65),
),
)
NETWORK_RECONNECT = ButtonWrapper(
name='NETWORK_RECONNECT',
jp=Button(
file='./assets/jp/base/popup/NETWORK_RECONNECT.png',
area=(725, 488, 810, 516),
search=(705, 468, 830, 536),
color=(78, 138, 169),
button=(663, 467, 870, 537),
),
)
+5
View File
@@ -0,0 +1,5 @@
from tasks.base.popup import PopupHandler
class MainPage(PopupHandler):
pass
+162
View File
@@ -0,0 +1,162 @@
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_GO_TO_PURCHASE)
# Cafe
page_cafe = Page(CAFE_CHECK)
page_cafe.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_CAFE, destination=page_cafe)
# Schedule
page_schedule = Page(SCHEDULE_CHECK)
page_schedule.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_SCHEDULE, destination=page_schedule)
# Circle
page_circle = Page(CIRCLE_CHECK)
page_circle.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_CIRCLE, destination=page_circle)
# Crafting Chamber
page_crafting = Page(CRAFTING_CHECK)
page_crafting.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_CRAFTING, destination=page_crafting)
# Shop
page_shop = Page(SHOP_CHECK)
page_shop.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_SHOP, destination=page_shop)
# Gacha
page_gacha = Page(GACHA_CHECK)
page_gacha.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_GACHA, destination=page_gacha)
# Account Info
page_account_info = Page(ACCOUNT_INFO_CHECK)
page_account_info.link(HOME, destination=page_main)
# Mail
page_mail = Page(MAIL_CHECK)
page_mail.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_MAIL, destination=page_mail)
# Task (Daily)
page_task = Page(TASK_CHECK)
page_task.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_TASK, destination=page_task)
# MomoTalk
page_momo_talk = Page(MOMOTALK_CHECK)
page_momo_talk.link(MOMOTALK_GO_TO_MAIN, destination=page_main)
page_main.link(MAIN_GO_TO_MOMOTALK, destination=page_momo_talk)
# Work
page_work = Page(WORK_CHECK)
page_work.link(HOME, destination=page_main)
page_main.link(MAIN_GO_TO_WORK, destination=page_work)
# Mission
page_mission = Page(MISSION_CHECK)
page_mission.link(HOME, destination=page_main)
page_mission.link(BACK, destination=page_work)
page_work.link(WORK_GO_TO_MISSION, destination=page_mission)
# Story
page_story = Page(STORY_CHECK)
page_story.link(HOME, destination=page_main)
page_story.link(BACK, destination=page_work)
page_work.link(WORK_GO_TO_STORY, destination=page_story)
# Bounty
page_bounty = Page(BOUNTY_CHECK)
page_bounty.link(HOME, destination=page_main)
page_bounty.link(BACK, destination=page_work)
page_work.link(WORK_GO_TO_BOUNTY, destination=page_bounty)
# Commissions
page_commissions = Page(COMMISSIONS_CHECK)
page_commissions.link(HOME, destination=page_main)
page_commissions.link(BACK, destination=page_work)
page_work.link(WORK_GO_TO_COMMISSIONS, destination=page_commissions)
# School Exchange
page_school_exchange = Page(SCHOOL_EXCHANGE_CHECK)
page_school_exchange.link(HOME, destination=page_main)
page_school_exchange.link(BACK, destination=page_work)
page_work.link(WORK_GO_TO_SCHOOL_EXCHANGE, destination=page_school_exchange)
# Tactical Challenge
page_tactical_challenge = Page(TACTICAL_CHALLENGE_CHECK)
page_tactical_challenge.link(HOME, destination=page_main)
page_tactical_challenge.link(BACK, destination=page_work)
page_work.link(WORK_GO_TO_TACTICAL_CHALLENGE, destination=page_tactical_challenge)
+91
View File
@@ -0,0 +1,91 @@
from module.base.base import ModuleBase
from module.base.timer import Timer
from tasks.base.assets.assets_base_popup import *
from tasks.base.assets.assets_base_page import LOADING_CHECK
class PopupHandler(ModuleBase):
def handle_loading(self, interval=5) -> bool:
"""
Args:
interval:
Returns:
If handled.
"""
if self.appear(LOADING_CHECK, interval=interval):
timer = Timer(0.5).start()
while 1:
if timer.reached_and_reset():
self.device.screenshot()
if self.appear(LOADING_CHECK):
self.device.stuck_record_clear()
continue
else:
break
return True
return False
def handle_reward(self, interval=5) -> bool:
"""
Args:
interval:
Returns:
If handled.
"""
if self.appear_then_click(GET_REWARD, interval=interval):
timer = Timer(0.2).start()
while 1:
if timer.reached_and_reset():
self.device.screenshot()
if self.appear(GET_REWARD):
self.device.click(GET_REWARD)
else:
break
return True
return False
def handle_reward_skip(self, interval=5) -> bool:
if self.appear_then_click(GET_REWARD_SKIP, interval=interval):
return True
def handle_daily_news(self, interval=5) -> bool:
if self.appear_then_click(DAILY_NEWS, interval=interval):
return True
return False
def handle_daily_reward(self, interval=5) -> bool:
if self.appear_then_click(DAILY_REWARD, interval=interval):
return True
return False
def handle_network_reconnect(self, interval=5) -> bool:
if self.appear_then_click(NETWORK_RECONNECT, interval=interval):
return True
return False
def handle_affection_level_up(self, interval=5) -> bool:
if self.appear_then_click(AFFECTION_LEVEL_UP, interval=interval):
timer = Timer(0.2).start()
while 1:
if timer.reached_and_reset():
self.device.screenshot()
if self.appear(AFFECTION_LEVEL_UP):
self.device.click(AFFECTION_LEVEL_UP)
else:
break
return True
return False
def handle_new_student(self, interval=5) -> bool:
if self.appear_then_click(GET_NEW_STUDENT, interval=interval):
return True
return False
+393
View File
@@ -0,0 +1,393 @@
from module.base.button import ButtonWrapper
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 module.ocr.ocr import Ocr
from tasks.base.main_page import MainPage
from tasks.base.page import Page, page_main
from tasks.base.assets.assets_base_page import BACK
class UI(MainPage):
ui_current: Page
ui_main_confirm_timer = Timer(0.2, count=2)
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():
logger.info("Additional ui page handled")
timeout.reset()
continue
logger.info("May be in standby main page")
self.device.click(BACK)
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("Lang", self.config.LANG)
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 AAS")
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()))
# loading_timer = Timer(0.5)
logger.hr(f"UI goto {destination}")
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# # Loading
# if self.appear(LOADING_CHECK):
# loading_timer.reset()
# continue
#
# if not loading_timer.reached():
# continue
# Destination page
if self.ui_page_appear(destination):
logger.info(f'Page arrive: {destination}')
if self.ui_page_confirm(destination):
logger.info(f'Page arrive confirm {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}')
# self.handle_lang_check(page)
if self.ui_page_confirm(page):
logger.info(f'Page arrive confirm {page}')
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, acquire_lang_checked=True, skip_first_screenshot=True):
"""
Args:
destination (Page):
acquire_lang_checked:
skip_first_screenshot:
Returns:
bool: If UI switched.
"""
logger.hr("UI ensure")
self.ui_get_current_page(skip_first_screenshot=skip_first_screenshot)
# self.ui_leave_special()
# if acquire_lang_checked:
# if self.acquire_lang_checked():
# 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_ensure_index(
self,
index,
letter,
next_button,
prev_button,
skip_first_screenshot=False,
fast=True,
interval=(0.2, 0.3),
):
"""
Args:
index (int):
letter (Ocr, callable): OCR button.
next_button (Button):
prev_button (Button):
skip_first_screenshot (bool):
fast (bool): Default true. False when index is not continuous.
interval (tuple, int, float): Seconds between two click.
"""
logger.hr("UI ensure index")
retry = Timer(1, count=2)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if isinstance(letter, Ocr):
current = letter.ocr_single_line(self.device.image)
else:
current = letter(self.device.image)
logger.attr("Index", current)
diff = index - current
if diff == 0:
break
if current == 0:
logger.warning(f'ui_ensure_index got an empty current value: {current}')
continue
if retry.reached():
button = next_button if diff > 0 else prev_button
if fast:
self.device.multi_click(button, n=abs(diff), interval=interval)
else:
self.device.click(button)
retry.reset()
def ui_click(
self,
click_button,
check_button,
appear_button=None,
additional=None,
retry_wait=5,
skip_first_screenshot=True,
):
"""
Args:
click_button (ButtonWrapper):
check_button (ButtonWrapper, callable, list[ButtonWrapper], tuple[ButtonWrapper]):
appear_button (ButtonWrapper, callable, list[ButtonWrapper], tuple[ButtonWrapper]):
additional (callable):
retry_wait (int, float):
skip_first_screenshot (bool):
"""
if appear_button is None:
appear_button = click_button
logger.info(f'UI click: {appear_button} -> {check_button}')
def process_appear(button):
if isinstance(button, ButtonWrapper):
return self.appear(button)
elif callable(button):
return button()
elif isinstance(button, (list, tuple)):
for b in button:
if self.appear(b):
return True
return False
else:
return self.appear(button)
click_timer = Timer(retry_wait, count=retry_wait // 0.5)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# End
if process_appear(check_button):
break
# Click
if click_timer.reached():
if process_appear(appear_button):
self.device.click(click_button)
click_timer.reset()
continue
if additional is not None:
if additional():
continue
def is_in_main(self):
if self.appear(page_main.check_button):
if self.image_color_count(page_main.check_button, color=(235, 235, 235), threshold=221, count=400):
return True
# if self.appear(MAP_EXIT):
# if self.image_color_count(MAP_EXIT, color=(235, 235, 235), threshold=221, count=50):
# return True
return False
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
# if self.handle_monthly_card_reward():
# return True
# if self.appear(COMBAT_PREPARE, interval=5):
# logger.info(f'UI additional: {COMBAT_PREPARE} -> {CLOSE}')
# self.device.click(CLOSE)
# if self.appear_then_click(COMBAT_EXIT, interval=5):
# return True
# if self.appear_then_click(INFO_CLOSE, interval=5):
# return True
#
# return False
def ui_additional(self) -> bool:
"""
Handle all possible popups during UI switching.
Returns:
If handled any popup.
"""
if self.handle_loading():
return True
if self.handle_reward_skip():
return True
if self.handle_reward():
return True
if self.handle_daily_news():
return True
if self.handle_network_reconnect():
return True
if self.handle_affection_level_up():
return True
if self.handle_new_student():
return True
return False
def _ui_button_confirm(
self,
button,
confirm=Timer(0.1, count=0),
timeout=Timer(2, count=6),
skip_first_screenshot=True
):
confirm.reset()
timeout.reset()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
logger.warning(f'_ui_button_confirm({button}) timeout')
break
if self.appear(button):
if confirm.reached():
break
else:
confirm.reset()
def ui_page_confirm(self, page):
"""
Args:
page (Page):
Returns:
bool: If handled
"""
if page == page_main:
self._ui_button_confirm(page.check_button)
return True
return False
def ui_button_interval_reset(self, button):
"""
Reset interval of some button to avoid mistaken clicks
Args:
button (Button):
"""
pass
+85
View File
@@ -0,0 +1,85 @@
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 ```
BOX_CAFE = ButtonWrapper(
name='BOX_CAFE',
jp=Button(
file='./assets/jp/cafe/BOX_CAFE.png',
area=(33, 130, 1247, 569),
search=(13, 110, 1267, 589),
color=(175, 181, 186),
button=(33, 130, 1247, 569),
),
)
CHECK_REWARD = ButtonWrapper(
name='CHECK_REWARD',
jp=Button(
file='./assets/jp/cafe/CHECK_REWARD.png',
area=(1095, 621, 1146, 637),
search=(1075, 601, 1166, 657),
color=(82, 105, 129),
button=(1086, 607, 1225, 685),
),
)
CLICKABLE_TEMPLATE = ButtonWrapper(
name='CLICKABLE_TEMPLATE',
jp=Button(
file='./assets/jp/cafe/CLICKABLE_TEMPLATE.png',
area=(0, 0, 42, 56),
search=(0, 0, 62, 76),
color=(77, 63, 1),
button=(0, 0, 42, 56),
),
)
GET_REWARD = ButtonWrapper(
name='GET_REWARD',
jp=Button(
file='./assets/jp/cafe/GET_REWARD.png',
area=(611, 514, 669, 543),
search=(591, 494, 689, 563),
color=(159, 133, 48),
button=(546, 494, 735, 563),
),
)
GET_REWARD_CLOSE = ButtonWrapper(
name='GET_REWARD_CLOSE',
jp=Button(
file='./assets/jp/cafe/GET_REWARD_CLOSE.png',
area=(891, 144, 917, 170),
search=(871, 124, 937, 190),
color=(173, 179, 189),
button=(891, 144, 917, 170),
),
)
GOT_REWARD = ButtonWrapper(
name='GOT_REWARD',
jp=Button(
file='./assets/jp/cafe/GOT_REWARD.png',
area=(609, 507, 672, 535),
search=(589, 487, 692, 555),
color=(174, 175, 174),
button=(609, 507, 672, 535),
),
)
OCR_CAFE = ButtonWrapper(
name='OCR_CAFE',
jp=Button(
file='./assets/jp/cafe/OCR_CAFE.png',
area=(1103, 642, 1202, 672),
search=(1083, 622, 1222, 692),
color=(87, 107, 129),
button=(1103, 642, 1202, 672),
),
)
STUDENT_LIST = ButtonWrapper(
name='STUDENT_LIST',
jp=Button(
file='./assets/jp/cafe/STUDENT_LIST.png',
area=(612, 172, 667, 201),
search=(592, 152, 687, 221),
color=(145, 157, 172),
button=(545, 423, 738, 495),
),
)
+227
View File
@@ -0,0 +1,227 @@
import cv2
import numpy as np
from enum import Enum
from module.logger import logger
from module.base.timer import Timer
from module.base.button import ClickButton
from module.base.utils.utils import area_offset
from module.ocr.ocr import Digit
from tasks.base.page import page_cafe
from tasks.base.ui import UI
from tasks.cafe.assets.assets_cafe import *
class CafeStatus(Enum):
STUDENT_LIST = 0
OCR = 1
REWARD = 2
GOT = 3
CLICK = 4
CHECK = 5
FINISHED = -1
class Cafe(UI):
@staticmethod
def merge_points(points, threshold=3):
if len(points) <= 1:
return points
result = []
for point in points:
if not result:
result.append(point)
continue
if point[0] - result[-1][0] < threshold and point[1] - result[-1][1] < threshold:
result[-1] = ((point[0] + result[-1][0]) // 2, (point[1] + result[-1][1]) // 2)
continue
result.append(point)
return result
@staticmethod
def _extract_clickable_from_image(image):
# convert to hsv for better color matching
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# set color range
lower_hsv = np.array([17, 200, 220])
upper_hsv = np.array([28, 255, 255])
# get mask
mask = cv2.inRange(hsv, lower_hsv, upper_hsv)
# generate result
return cv2.bitwise_and(image, image, mask=mask)
def _match_clickable_points(self, image, threshold=0.9):
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template = self.btn.matched_button.image
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
loc = np.where(res >= threshold)
return [point for point in zip(*loc[::-1])]
def _get_clickable_buttons(self, threshold=0.9, offset=(0, 0)):
image = cv2.copyMakeBorder(self.device.image, 20, 20, 10, 60, cv2.BORDER_CONSTANT, value=(0, 0, 0))
image = self._extract_clickable_from_image(image)
points = self._match_clickable_points(image, threshold)
points = self.merge_points(points)
if not points:
return []
area = area_offset((0, 0, self.btn.width, self.btn.height), offset)
return [
ClickButton(
button=area_offset(area, offset=point),
name=self.btn.name
)
for point in points
]
def _reset_cafe_position(self, direction: str):
width = BOX_CAFE.width
height = BOX_CAFE.height
r = np.random.uniform(0.6, 0.8)
vector_down = (width * r, height * r)
vector_up = (width * r, -height * r)
vector_left = (-width * r, 0)
vector_right = (width * r, 0)
random_r = (-5, -5, 5, 5)
match direction:
case 'init':
self.device.pinch()
self.device.swipe_vector(vector_down, box=BOX_CAFE.area, random_range=random_r, padding=5)
self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5)
self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5)
self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5)
case 'left':
self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5)
self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5)
case 'right':
self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5)
self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5)
def _get_reward_num(self):
ocr = Digit(OCR_CAFE)
num = ocr.detect_and_ocr(self.device.image)
if len(num) != 1:
logger.warning(f'Invalid reward num: {num}')
num = float(num[0].ocr_text.rstrip('%'))
logger.attr('Reward', num)
return num
def _handle_cafe(self, status):
match status:
case CafeStatus.STUDENT_LIST:
self.appear_then_click(STUDENT_LIST)
if not self.appear(STUDENT_LIST):
return CafeStatus.OCR
case CafeStatus.OCR:
reward = self._get_reward_num()
if reward == 0:
return CafeStatus.GOT
if self.appear_then_click(CHECK_REWARD):
return CafeStatus.REWARD
case CafeStatus.REWARD:
if self.match_color(GOT_REWARD):
self.device.click(GET_REWARD_CLOSE)
return CafeStatus.GOT
if not self.appear(GET_REWARD):
return CafeStatus.OCR
if self.match_color(GET_REWARD):
self.device.click(GET_REWARD)
case CafeStatus.GOT:
logger.info('Cafe reward have been got')
self.appear_then_click(GET_REWARD_CLOSE)
return CafeStatus.CLICK
case CafeStatus.CLICK:
buttons = self._get_clickable_buttons(offset=(45, 10))
self.click = len(buttons)
logger.attr('Clickable', self.click)
if not buttons:
return CafeStatus.CHECK
self.device.click(buttons[0])
case CafeStatus.CHECK:
buttons = self._get_clickable_buttons()
if not self.is_adjust_on:
if not buttons:
return CafeStatus.FINISHED
else:
return CafeStatus.CLICK
if not buttons:
self.check += 1
else:
self.check = 0
return CafeStatus.CLICK
match self.check:
case 1:
self._reset_cafe_position('left')
case 2:
self._reset_cafe_position('right')
case 3:
self._reset_cafe_position('init')
case 4:
return CafeStatus.FINISHED
case CafeStatus.FINISHED:
return status
case _:
logger.warning(f'Invalid status: {status}')
return status
def run(self):
self.btn = CLICKABLE_TEMPLATE
self.click = 0
self.check = 0
is_reward_on = self.config.Cafe_Reward
is_touch_on = self.config.Cafe_Touch
self.is_adjust_on = self.config.Cafe_AutoAdjust
self.ui_ensure(page_cafe)
status = CafeStatus.STUDENT_LIST
loading_timer = Timer(2).start()
action_timer = Timer(1.5, count=1) # cant be too fast
check_timer = Timer(1, count=1)
is_list = False
is_reset = False
is_enable = is_reward_on or is_touch_on
while is_enable:
self.device.screenshot()
if self.ui_additional():
continue
if not loading_timer.reached():
continue
if not is_list and status == CafeStatus.STUDENT_LIST and self.appear(STUDENT_LIST):
is_list = True
loading_timer = Timer(5).start()
continue
if not is_reward_on and status == CafeStatus.OCR:
logger.info('Skip reward')
status = CafeStatus.CLICK
continue
if not is_touch_on and status == CafeStatus.CLICK:
logger.info('Skip touch')
status = CafeStatus.FINISHED
continue
if is_touch_on and not is_reset and status == CafeStatus.CLICK:
self._reset_cafe_position('init')
is_reset = True
continue
if status == CafeStatus.CHECK and not check_timer.reached_and_reset():
continue
if action_timer.reached_and_reset():
status = self._handle_cafe(status)
logger.attr('Status', status)
if status is CafeStatus.FINISHED:
break
self.config.task_delay(server_update=True, minute=180)
+15
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 ```
GET_REWARD_AP = ButtonWrapper(
name='GET_REWARD_AP',
jp=Button(
file='./assets/jp/circle/GET_REWARD_AP.png',
area=(540, 148, 623, 175),
search=(520, 128, 643, 195),
color=(198, 206, 213),
button=(543, 457, 735, 524),
),
)
+56
View File
@@ -0,0 +1,56 @@
from enum import Enum
from module.base.timer import Timer
from module.logger import logger
from tasks.base.page import page_circle
from tasks.base.ui import UI
from tasks.circle.assets.assets_circle import *
class CircleStatus(Enum):
"""
Circle status
"""
REWARD = 0
GOT = 1
FINISHED = -1
class Circle(UI):
def _handle_circle(self, status):
match status:
case CircleStatus.REWARD:
if self.appear_then_click(GET_REWARD_AP):
logger.info("Get circle AP reward")
return CircleStatus.FINISHED
case CircleStatus.GOT:
logger.info("Circle AP reward have been got")
return CircleStatus.FINISHED
case _:
logger.warning(f"Invalid status: {status}")
return status
def run(self):
self.ui_ensure(page_circle)
status = CircleStatus.REWARD
action_timer = Timer(0.5)
ap_timer = Timer(2).start()
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if ap_timer.reached() and status == CircleStatus.REWARD:
status = CircleStatus.GOT
if action_timer.reached_and_reset():
status = self._handle_circle(status)
logger.attr('Status', status)
if status is CircleStatus.FINISHED:
break
self.config.task_delay(server_update=True)
+25
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 ```
OCR_AP = ButtonWrapper(
name='OCR_AP',
share=Button(
file='./assets/share/item/data/OCR_AP.png',
area=(560, 11, 667, 37),
search=(540, 0, 687, 57),
color=(211, 216, 219),
button=(560, 11, 667, 37),
),
)
OCR_DATA = ButtonWrapper(
name='OCR_DATA',
share=Button(
file='./assets/share/item/data/OCR_DATA.png',
area=(768, 12, 1072, 37),
search=(748, 0, 1092, 57),
color=(212, 220, 224),
button=(768, 12, 1072, 37),
),
)
+47
View File
@@ -0,0 +1,47 @@
from module.base.timer import Timer
from module.logger import logger
from module.ocr.ocr import Digit, DigitCounter
from tasks.base.page import page_work
from tasks.base.ui import UI
from tasks.item.assets.assets_item_data import *
class DataUpdate(UI):
def _get_data(self):
"""
Page:
in: page_work
"""
ap = DigitCounter(OCR_AP).ocr_single_line(self.device.image)
# Data for Credit and Pyroxene
ocr = Digit(OCR_DATA)
timeout = Timer(2, count=6).start()
while 1:
data = ocr.detect_and_ocr(self.device.image)
if len(data) != 2:
data = [data[0], data[-1]]
logger.attr('Data', data)
credit, pyroxene = [int(''.join([v for v in d.ocr_text if v.isdigit()])) for d in data]
if credit > 0 or pyroxene > 0:
break
logger.warning(f'Invalid credit and pyroxene: {data}')
if timeout.reached():
logger.warning('Get data timeout')
break
logger.attr('Credit', credit)
logger.attr('Pyroxene', pyroxene)
with self.config.multi_set():
self.config.stored.AP.set(ap[0], ap[2])
self.config.stored.Credit.value = credit
self.config.stored.Pyroxene.value = pyroxene
return ap, credit, pyroxene
def run(self):
self.ui_ensure(page_work, acquire_lang_checked=False)
with self.config.multi_set():
self._get_data()
self.config.task_delay(server_update=True)
+25
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 ```
LOGIN_CONFIRM = ButtonWrapper(
name='LOGIN_CONFIRM',
jp=Button(
file='./assets/jp/login/LOGIN_CONFIRM.png',
area=(36, 621, 98, 654),
search=(16, 601, 118, 674),
color=(211, 215, 220),
button=(990, 354, 1232, 506),
),
)
LOGIN_LOADING = ButtonWrapper(
name='LOGIN_LOADING',
jp=Button(
file='./assets/jp/login/LOGIN_LOADING.png',
area=(34, 682, 60, 707),
search=(14, 662, 80, 720),
color=(8, 66, 96),
button=(34, 682, 60, 707),
),
)
+109
View File
@@ -0,0 +1,109 @@
from module.base.timer import Timer
from module.exception import GameNotRunningError
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, USER_AGREEMENT_ACCEPT, LOGIN_LOADING
from tasks.login.assets.assets_login import LOGIN_CONFIRM, LOGIN_LOADING
class Login(UI):
def _handle_app_login(self):
"""
Pages:
in: Any page
out: page_main
Raises:
GameStuckError:
GameTooManyClickError:
GameNotRunningError:
"""
def _page_main_twice_confirm():
if self.ui_page_appear(page_main):
timer = Timer(1).start()
while 1:
if timer.reached():
self.device.screenshot()
if self.ui_page_appear(page_main):
logger.info('Login to main confirm')
return True
return False
logger.hr('App login')
orientation_timer = Timer(5)
startup_timer = Timer(5).start()
app_timer = Timer(5).start()
login_success = False
while 1:
# Watch if game alive
if app_timer.reached():
if not self.device.app_is_running():
logger.error('Game died during launch')
raise GameNotRunningError('Game not running')
app_timer.reset()
# 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
# Game client requires at least 5s to start
# The first few frames might be captured before app_stop(), ignore them
if startup_timer.reached():
if self.handle_daily_reward() or self.handle_daily_news():
startup_timer.reset() # reuse startup timer as daily reward timer
continue
if _page_main_twice_confirm():
break
# Watch resource downloading and loading
if self.match_color(LOGIN_LOADING, interval=5, threshold=45):
logger.info('Game resources downloading or loading')
self.device.stuck_record_clear()
# Login
if self.appear_then_click(LOGIN_CONFIRM):
login_success = True
continue
# if self.appear_then_click(USER_AGREEMENT_ACCEPT):
# continue
# Additional
# if self.handle_popup_single():
# continue
# if self.handle_popup_confirm():
# continue
if self.ui_additional():
continue
return True
def handle_app_login(self):
logger.info('handle_app_login')
self.device.screenshot_interval_set(1.0)
self.device.stuck_timer = Timer(300, count=300).start()
try:
self._handle_app_login()
finally:
self.device.screenshot_interval_set()
self.device.stuck_timer = Timer(60, count=60).start()
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)
+25
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 ```
MAIL_RECEIVE = ButtonWrapper(
name='MAIL_RECEIVE',
jp=Button(
file='./assets/jp/mail/MAIL_RECEIVE.png',
area=(1086, 659, 1183, 682),
search=(1066, 639, 1203, 702),
color=(174, 151, 53),
button=(1043, 640, 1226, 701),
),
)
MAIL_RECEIVED = ButtonWrapper(
name='MAIL_RECEIVED',
jp=Button(
file='./assets/jp/mail/MAIL_RECEIVED.png',
area=(1085, 657, 1186, 685),
search=(1065, 637, 1206, 705),
color=(195, 196, 196),
button=(1085, 657, 1186, 685),
),
)
+26
View File
@@ -0,0 +1,26 @@
from module.base.timer import Timer
from module.logger import logger
from tasks.base.page import page_mail
from tasks.base.ui import UI
from tasks.mail.assets.assets_mail import *
class Mail(UI):
def run(self):
self.ui_ensure(page_mail)
action_timer = Timer(1).start()
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if action_timer.reached_and_reset():
if self.match_color(MAIL_RECEIVE):
self.device.click(MAIL_RECEIVE)
logger.info("Receive mail")
continue
if self.appear(MAIL_RECEIVED):
logger.info("Mail have been received")
break
self.config.task_delay(server_update=True)
+15
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 ```
SCROLL = ButtonWrapper(
name='SCROLL',
jp=Button(
file='./assets/jp/schedule/SCROLL.png',
area=(742, 136, 1101, 671),
search=(722, 116, 1121, 691),
color=(198, 214, 210),
button=(742, 136, 1101, 671),
),
)
@@ -0,0 +1,145 @@
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 ```
CHALLENGE_LOSE = ButtonWrapper(
name='CHALLENGE_LOSE',
jp=Button(
file='./assets/jp/tactical_challenge/CHALLENGE_LOSE.png',
area=(583, 195, 698, 224),
search=(563, 175, 718, 244),
color=(146, 158, 173),
button=(548, 431, 738, 499),
),
)
CHALLENGE_WIN = ButtonWrapper(
name='CHALLENGE_WIN',
jp=Button(
file='./assets/jp/tactical_challenge/CHALLENGE_WIN.png',
area=(583, 131, 698, 160),
search=(563, 111, 718, 180),
color=(151, 165, 180),
button=(549, 495, 737, 563),
),
)
GET_REWARD_CREDIT = ButtonWrapper(
name='GET_REWARD_CREDIT',
jp=Button(
file='./assets/jp/tactical_challenge/GET_REWARD_CREDIT.png',
area=(308, 360, 397, 415),
search=(288, 340, 417, 435),
color=(215, 193, 61),
button=(308, 360, 397, 415),
),
)
GET_REWARD_DAILY = ButtonWrapper(
name='GET_REWARD_DAILY',
jp=Button(
file='./assets/jp/tactical_challenge/GET_REWARD_DAILY.png',
area=(307, 440, 395, 493),
search=(287, 420, 415, 513),
color=(214, 192, 60),
button=(307, 440, 395, 493),
),
)
GOT_REWARD_CREDIT = ButtonWrapper(
name='GOT_REWARD_CREDIT',
jp=Button(
file='./assets/jp/tactical_challenge/GOT_REWARD_CREDIT.png',
area=(308, 358, 398, 416),
search=(288, 338, 418, 436),
color=(203, 205, 204),
button=(308, 358, 398, 416),
),
)
GOT_REWARD_DAILY = ButtonWrapper(
name='GOT_REWARD_DAILY',
jp=Button(
file='./assets/jp/tactical_challenge/GOT_REWARD_DAILY.png',
area=(307, 439, 397, 494),
search=(287, 419, 417, 514),
color=(203, 203, 204),
button=(307, 439, 397, 494),
),
)
OCR_TICKET = ButtonWrapper(
name='OCR_TICKET',
jp=Button(
file='./assets/jp/tactical_challenge/OCR_TICKET.png',
area=(206, 479, 240, 499),
search=(186, 459, 260, 519),
color=(200, 202, 204),
button=(206, 479, 240, 499),
),
)
PLAYER_SELECT_FIRST = ButtonWrapper(
name='PLAYER_SELECT_FIRST',
jp=Button(
file='./assets/jp/tactical_challenge/PLAYER_SELECT_FIRST.png',
area=(467, 293, 496, 316),
search=(447, 273, 516, 336),
color=(191, 202, 207),
button=(558, 179, 1118, 282),
),
)
PLAYER_SELECT_SECOND = ButtonWrapper(
name='PLAYER_SELECT_SECOND',
jp=Button(
file='./assets/jp/tactical_challenge/PLAYER_SELECT_SECOND.png',
area=(466, 451, 496, 475),
search=(446, 431, 516, 495),
color=(192, 203, 208),
button=(554, 336, 1127, 443),
),
)
PLAYER_SELECT_THIRD = ButtonWrapper(
name='PLAYER_SELECT_THIRD',
jp=Button(
file='./assets/jp/tactical_challenge/PLAYER_SELECT_THIRD.png',
area=(466, 608, 496, 634),
search=(446, 588, 516, 654),
color=(194, 206, 211),
button=(557, 495, 1120, 599),
),
)
PREPARE_CHALLENGE = ButtonWrapper(
name='PREPARE_CHALLENGE',
jp=Button(
file='./assets/jp/tactical_challenge/PREPARE_CHALLENGE.png',
area=(583, 83, 698, 111),
search=(563, 63, 718, 131),
color=(148, 161, 175),
button=(561, 548, 723, 598),
),
)
SKIP_OFF = ButtonWrapper(
name='SKIP_OFF',
jp=Button(
file='./assets/jp/tactical_challenge/SKIP_OFF.png',
area=(1087, 585, 1127, 622),
search=(1067, 565, 1147, 642),
color=(96, 137, 171),
button=(1088, 582, 1250, 626),
),
)
SKIP_ON = ButtonWrapper(
name='SKIP_ON',
jp=Button(
file='./assets/jp/tactical_challenge/SKIP_ON.png',
area=(1096, 591, 1121, 615),
search=(1076, 571, 1141, 635),
color=(99, 177, 212),
button=(1089, 581, 1250, 626),
),
)
START_CHALLENGE = ButtonWrapper(
name='START_CHALLENGE',
jp=Button(
file='./assets/jp/tactical_challenge/START_CHALLENGE.png',
area=(107, 10, 159, 37),
search=(87, 0, 179, 57),
color=(148, 161, 176),
button=(1091, 635, 1246, 704),
),
)
@@ -0,0 +1,157 @@
import random
from enum import Enum
from module.base.timer import Timer
from module.logger import logger
from module.ocr.ocr import DigitCounter
from module.ui.switch import Switch
from tasks.base.page import page_tactical_challenge
from tasks.base.ui import UI
from tasks.tactical_challenge.assets.assets_tactical_challenge import *
SWITCH_SKIP = Switch('Skip_switch')
SWITCH_SKIP.add_state('on', SKIP_ON)
SWITCH_SKIP.add_state('off', SKIP_OFF)
class TCStatus(Enum):
"""
Tactical challenge status
"""
REWARD = 0
OCR = 1
SELECT = 2
PREPARE = 3
SKIP = 4
START = 5
RESULT = 6
WIN = 7
LOSE = 8
FINAL = 9
FINISHED = -1
class TacticalChallenge(UI):
select_players = (PLAYER_SELECT_FIRST, PLAYER_SELECT_SECOND, PLAYER_SELECT_THIRD)
def _get_ticket(self):
"""
Page:
in: page_tactical_challenge
"""
ocr = DigitCounter(OCR_TICKET).ocr_single_line(self.device.image)
# number of tickets remaining
ticket, _, total = ocr
if total == 0:
logger.warning('Invalid ticket')
return False, 5
logger.attr('Ticket', ticket)
return True, ticket
def _get_reward(self):
if self.match_color(GET_REWARD_DAILY):
self.device.click(GET_REWARD_DAILY)
logger.info('Get tc daily reward')
return True
if self.match_color(GET_REWARD_CREDIT):
self.device.click(GET_REWARD_CREDIT)
logger.info('Get tc credit reward')
return True
if self.match_color(GOT_REWARD_DAILY) and self.match_color(GOT_REWARD_CREDIT):
logger.info('Both tc reward got')
return True
return False
def _set_skip(self):
"""
Set skip switch to on
:returns: True if switch is set, False if switch not found
"""
if not SWITCH_SKIP.appear(main=self):
logger.info('Skip switch not found')
return False
SWITCH_SKIP.set('on', main=self)
return True
def _player_select(self, select):
if select:
return random.choice(self.select_players)
else:
return self.select_players[select]
def _handle_challenge(self, status):
match status:
case TCStatus.REWARD:
if self._get_reward():
return TCStatus.OCR
case TCStatus.OCR:
is_valid, ticket = self._get_ticket()
if not is_valid:
return status
if ticket == 0:
return TCStatus.FINISHED
return TCStatus.SELECT
case TCStatus.SELECT:
self.appear_then_click(self.select)
if self.appear(PREPARE_CHALLENGE):
return TCStatus.PREPARE
case TCStatus.PREPARE:
self.appear_then_click(PREPARE_CHALLENGE)
if not self.appear(PREPARE_CHALLENGE):
return TCStatus.SKIP
case TCStatus.SKIP:
if not self._set_skip():
return TCStatus.SKIP
return TCStatus.START
case TCStatus.START:
self.appear_then_click(START_CHALLENGE)
if not self.appear(START_CHALLENGE):
return TCStatus.RESULT
case TCStatus.RESULT:
if self.appear_then_click(CHALLENGE_WIN):
return TCStatus.WIN
if self.appear_then_click(CHALLENGE_LOSE):
return TCStatus.LOSE
case TCStatus.WIN | TCStatus.LOSE:
if self.appear(CHALLENGE_WIN) or self.appear(CHALLENGE_LOSE):
return TCStatus.RESULT
is_valid, ticket = self._get_ticket()
if not is_valid:
return status
if ticket == 0:
return TCStatus.FINISHED
return TCStatus.FINAL
case TCStatus.FINAL | TCStatus.FINISHED:
return status
case _:
logger.warning(f'Invalid status: {status}')
return status
def run(self):
self.ui_ensure(page_tactical_challenge)
self.select = self._player_select(self.config.TacticalChallenge_PlayerSelect)
status = TCStatus.REWARD
action_timer = Timer(1, count=1)
# ensure reward can be clicked
ui_timer = Timer(2).start()
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if not ui_timer.reached():
continue
if action_timer.reached_and_reset():
status = self._handle_challenge(status)
logger.attr('Status', status.name)
if status in (TCStatus.FINAL, TCStatus.FINISHED):
break
if status is TCStatus.FINISHED:
self.config.task_delay(server_update=True)
else:
self.config.task_delay(minute=1)