1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2025-12-16 22:05:12 +00:00

Compare commits

...

16 Commits

43 changed files with 680 additions and 36 deletions

View File

@ -12,12 +12,15 @@
The script is still under active development. The following features have been implemented:
- [x] **Cafe** Claim rewards / Interact / Second cafe
- [x] **Circle** Claim AP
- [x] **Cafe** Claim rewards / Interact / Second floor
- [x] **Club** Claim AP
- [x] **Mailbox** Claim rewards
- [x] **Tactical Challenge** Claim rewards / Auto battle
_Currently only supports JP server._
Supported servers:
- [x] JP
- [x] OVERSEA - Global
## Relative projects
@ -38,3 +41,5 @@ Thanks to [6bir](https://github.com/6bir) for the icon design.
Thanks to [Alas](https://github.com/LmeSzinc/AzurLaneAutoScript) and [SRC](https://github.com/LmeSzinc/StarRailCopilot)
for the development framework.
Thanks to [RedDeadDepresso](https://github.com/RedDeadDepresso) for EN support.

View File

@ -17,7 +17,10 @@
- [x] **邮箱** 领取奖励
- [x] **战术对抗赛** 领取奖励 / 自动战斗
_目前仅支持日服。_
目前支持的服务器:
- [x] 日服
- [x] 国际服 - 全球
## 相关项目
@ -36,3 +39,5 @@ _目前仅支持日服。_
感谢 [Alas](https://github.com/LmeSzinc/AzurLaneAutoScript) 以及 [SRC](https://github.com/LmeSzinc/StarRailCopilot)
提供的开发框架。
感谢 [RedDeadDepresso](https://github.com/RedDeadDepresso) 提供英语支持。

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -205,8 +205,8 @@
"help": "Auto adjust cafe interface for better student interaction"
},
"SecondCafe": {
"name": "Second Cafe",
"help": "Enable auto switch to second cafe and perform interaction"
"name": "Second Floor",
"help": "JP server only\nEnable auto switch to second floor and perform interaction"
}
},
"TacticalChallenge": {

View File

@ -206,7 +206,7 @@
},
"SecondCafe": {
"name": "第二咖啡厅",
"help": "自动切换第二咖啡厅进行互动点击"
"help": "仅支持日服\n自动切换第二咖啡厅进行互动点击"
}
},
"TacticalChallenge": {

View File

@ -863,8 +863,8 @@ class Connection(ConnectionAttr):
# Auto package detection
if len(packages) == 0:
logger.critical(f'No Star Rail package found, '
f'please confirm Star Rail has been installed on device "{self.serial}"')
logger.critical(f'No Blue Archive package found, '
f'please confirm Blue Archive has been installed on device "{self.serial}"')
raise RequestHumanTakeover
if len(packages) == 1:
logger.info('Auto package detection found only one package, using it')
@ -877,6 +877,6 @@ class Connection(ConnectionAttr):
# set_server(self.package)
else:
logger.critical(
f'Multiple Star Rail packages found, auto package detection cannot decide which to choose, '
f'Multiple Blue Archive packages found, auto package detection cannot decide which to choose, '
'please copy one of the available devices listed above to Alas.Emulator.PackageName')
raise RequestHumanTakeover

View File

@ -0,0 +1,71 @@
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 ```
CHECK_DESERT_RAILROAD = ButtonWrapper(
name='CHECK_DESERT_RAILROAD',
jp=Button(
file='./assets/jp/bounty/CHECK_DESERT_RAILROAD.png',
area=(106, 147, 401, 179),
search=(86, 127, 421, 199),
color=(172, 179, 183),
button=(106, 147, 401, 179),
),
en=None,
)
CHECK_HIGHWAY = ButtonWrapper(
name='CHECK_HIGHWAY',
jp=Button(
file='./assets/jp/bounty/CHECK_HIGHWAY.png',
area=(107, 147, 400, 179),
search=(87, 127, 420, 199),
color=(191, 199, 203),
button=(107, 147, 400, 179),
),
en=None,
)
CHECK_SCHOOLHOUSE = ButtonWrapper(
name='CHECK_SCHOOLHOUSE',
jp=Button(
file='./assets/jp/bounty/CHECK_SCHOOLHOUSE.png',
area=(106, 147, 314, 179),
search=(86, 127, 334, 199),
color=(176, 183, 187),
button=(106, 147, 314, 179),
),
en=None,
)
SELECT_DESERT_RAILROAD = ButtonWrapper(
name='SELECT_DESERT_RAILROAD',
jp=Button(
file='./assets/jp/bounty/SELECT_DESERT_RAILROAD.png',
area=(1066, 271, 1224, 311),
search=(1046, 251, 1244, 331),
color=(178, 188, 199),
button=(1066, 271, 1224, 311),
),
en=None,
)
SELECT_HIGHWAY = ButtonWrapper(
name='SELECT_HIGHWAY',
jp=Button(
file='./assets/jp/bounty/SELECT_HIGHWAY.png',
area=(1065, 165, 1223, 203),
search=(1045, 145, 1243, 223),
color=(214, 221, 228),
button=(1065, 165, 1223, 203),
),
en=None,
)
SELECT_SCHOOLHOUSE = ButtonWrapper(
name='SELECT_SCHOOLHOUSE',
jp=Button(
file='./assets/jp/bounty/SELECT_SCHOOLHOUSE.png',
area=(1154, 381, 1223, 417),
search=(1134, 361, 1243, 437),
color=(173, 185, 198),
button=(1154, 381, 1223, 417),
),
en=None,
)

View File

@ -5,6 +5,7 @@ from enum import Enum
from module.logger import logger
from module.base.timer import Timer
from module.base.button import ClickButton
from module.base.decorator import Config
from module.base.utils.utils import area_offset
from module.ocr.ocr import Digit
from module.ui.switch import Switch
@ -33,6 +34,8 @@ class CafeStatus(Enum):
class Cafe(UI):
template = CLICKABLE_TEMPLATE
@staticmethod
def merge_points(points, threshold=3):
if len(points) <= 1:
@ -51,8 +54,7 @@ class Cafe(UI):
@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)
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
# set color range
lower_hsv = np.array([18, 200, 220])
upper_hsv = np.array([30, 255, 255])
@ -62,8 +64,8 @@ class Cafe(UI):
return cv2.bitwise_and(image, image, mask=mask)
def _match_clickable_points(self, image, threshold=0.8):
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template = cv2.cvtColor(self.btn.matched_button.image, cv2.COLOR_BGR2GRAY)
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
template = cv2.cvtColor(self.template.matched_button.image, cv2.COLOR_RGB2GRAY)
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
loc = np.where(res >= threshold)
@ -78,11 +80,11 @@ class Cafe(UI):
points = self.merge_points(points)
if not points:
return []
area = area_offset((0, 0, self.btn.width, self.btn.height), offset)
area = area_offset((0, 0, self.template.width, self.template.height), offset)
return [
ClickButton(
button=area_offset(area, offset=point),
name=self.btn.name
name=self.template.name
)
for point in points
]
@ -188,15 +190,23 @@ class Cafe(UI):
logger.warning(f'Invalid status: {status}')
return status
@Config.when(Emulator_GameLanguage='jp')
def is_second_cafe_on(self):
return self.config.Cafe_SecondCafe
@Config.when(Emulator_GameLanguage=None)
def is_second_cafe_on(self):
return False
is_second_cafe_on = property(is_second_cafe_on)
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
is_second_cafe_on = self.config.Cafe_SecondCafe
self.ui_ensure(page_cafe)
@ -240,7 +250,7 @@ class Cafe(UI):
is_reset = True
continue
if is_second_cafe_on and not is_second and status == CafeStatus.FINISHED:
if self.is_second_cafe_on and not is_second and status == CafeStatus.FINISHED:
if not SWITCH_CAFE.appear(main=self):
logger.warning('Cafe switch not found')
continue
@ -266,11 +276,14 @@ class Cafe(UI):
logger.attr('Status', status)
status = self._handle_cafe(status)
if not is_second_cafe_on:
if not self.is_second_cafe_on:
if status is CafeStatus.FINISHED:
logger.info('Second cafe is not supported or disabled')
logger.info('Cafe finished')
break
else:
if is_second and status is CafeStatus.FINISHED:
logger.info('Cafe finished')
break
self.config.task_delay(server_update=True, minute=180)

View File

@ -0,0 +1,71 @@
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 ```
CHECK_GEHENNA = ButtonWrapper(
name='CHECK_GEHENNA',
jp=Button(
file='./assets/jp/school_exchange/CHECK_GEHENNA.png',
area=(109, 149, 195, 177),
search=(89, 129, 215, 197),
color=(201, 205, 206),
button=(109, 149, 195, 177),
),
en=None,
)
CHECK_MILLENNIUM = ButtonWrapper(
name='CHECK_MILLENNIUM',
jp=Button(
file='./assets/jp/school_exchange/CHECK_MILLENNIUM.png',
area=(108, 148, 254, 178),
search=(88, 128, 274, 198),
color=(202, 206, 208),
button=(108, 148, 254, 178),
),
en=None,
)
CHECK_TRINITY = ButtonWrapper(
name='CHECK_TRINITY',
jp=Button(
file='./assets/jp/school_exchange/CHECK_TRINITY.png',
area=(115, 148, 250, 178),
search=(95, 128, 270, 198),
color=(204, 208, 210),
button=(115, 148, 250, 178),
),
en=None,
)
SELECT_GEHENNA = ButtonWrapper(
name='SELECT_GEHENNA',
jp=Button(
file='./assets/jp/school_exchange/SELECT_GEHENNA.png',
area=(1125, 275, 1224, 310),
search=(1105, 255, 1244, 330),
color=(207, 217, 225),
button=(1125, 275, 1224, 310),
),
en=None,
)
SELECT_MILLENNIUM = ButtonWrapper(
name='SELECT_MILLENNIUM',
jp=Button(
file='./assets/jp/school_exchange/SELECT_MILLENNIUM.png',
area=(1069, 381, 1217, 417),
search=(1049, 361, 1237, 437),
color=(206, 214, 222),
button=(1069, 381, 1217, 417),
),
en=None,
)
SELECT_TRINITY = ButtonWrapper(
name='SELECT_TRINITY',
jp=Button(
file='./assets/jp/school_exchange/SELECT_TRINITY.png',
area=(1074, 165, 1221, 204),
search=(1054, 145, 1241, 224),
color=(213, 221, 228),
button=(1074, 165, 1221, 204),
),
en=None,
)

View File

@ -0,0 +1,60 @@
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_INDEX = ButtonWrapper(
name='OCR_INDEX',
jp=Button(
file='./assets/jp/stage/list/OCR_INDEX.png',
area=(701, 149, 740, 656),
search=(681, 129, 760, 676),
color=(195, 196, 193),
button=(701, 149, 740, 656),
),
en=None,
)
STAGE_ENTER = ButtonWrapper(
name='STAGE_ENTER',
jp=Button(
file='./assets/jp/stage/list/STAGE_ENTER.png',
area=(1093, 173, 1142, 199),
search=(1073, 153, 1162, 219),
color=(106, 171, 200),
button=(1093, 173, 1142, 199),
),
en=None,
)
STAGE_ITEM = ButtonWrapper(
name='STAGE_ITEM',
jp=Button(
file='./assets/jp/stage/list/STAGE_ITEM.png',
area=(687, 148, 1181, 227),
search=(667, 128, 1201, 247),
color=(212, 228, 233),
button=(687, 148, 1181, 227),
),
en=None,
)
STAGE_LIST = ButtonWrapper(
name='STAGE_LIST',
jp=Button(
file='./assets/jp/stage/list/STAGE_LIST.png',
area=(675, 136, 1190, 676),
search=(655, 116, 1210, 696),
color=(194, 204, 209),
button=(675, 136, 1190, 676),
),
en=None,
)
STAGE_STARS = ButtonWrapper(
name='STAGE_STARS',
jp=Button(
file='./assets/jp/stage/list/STAGE_STARS.png',
area=(693, 192, 746, 212),
search=(673, 172, 766, 232),
color=(225, 214, 166),
button=(693, 192, 746, 212),
),
en=None,
)

View File

@ -0,0 +1,148 @@
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 ```
CHECK_SWEEP = ButtonWrapper(
name='CHECK_SWEEP',
jp=Button(
file='./assets/jp/stage/sweep/CHECK_SWEEP.png',
area=(638, 188, 722, 212),
search=(618, 168, 742, 232),
color=(174, 184, 197),
button=(638, 188, 722, 212),
),
en=None,
)
ENTER = ButtonWrapper(
name='ENTER',
jp=Button(
file='./assets/jp/stage/sweep/ENTER.png',
area=(791, 514, 1080, 568),
search=(771, 494, 1100, 588),
color=(223, 207, 68),
button=(791, 514, 1080, 568),
),
en=None,
)
EXIT = ButtonWrapper(
name='EXIT',
jp=Button(
file='./assets/jp/stage/sweep/EXIT.png',
area=(1114, 127, 1141, 154),
search=(1094, 107, 1161, 174),
color=(185, 193, 203),
button=(1114, 127, 1141, 154),
),
en=None,
)
MAX = ButtonWrapper(
name='MAX',
jp=Button(
file='./assets/jp/stage/sweep/MAX.png',
area=(1054, 279, 1111, 321),
search=(1034, 259, 1131, 341),
color=(216, 222, 228),
button=(1054, 279, 1111, 321),
),
en=None,
)
MIN = ButtonWrapper(
name='MIN',
jp=Button(
file='./assets/jp/stage/sweep/MIN.png',
area=(760, 278, 816, 322),
search=(740, 258, 836, 342),
color=(194, 194, 194),
button=(760, 278, 816, 322),
),
en=None,
)
MINUS = ButtonWrapper(
name='MINUS',
jp=Button(
file='./assets/jp/stage/sweep/MINUS.png',
area=(838, 279, 876, 320),
search=(818, 259, 896, 340),
color=(221, 222, 222),
button=(838, 279, 876, 320),
),
en=None,
)
OCR_NUM = ButtonWrapper(
name='OCR_NUM',
jp=Button(
file='./assets/jp/stage/sweep/OCR_NUM.png',
area=(896, 281, 975, 323),
search=(876, 261, 995, 343),
color=(81, 94, 113),
button=(896, 281, 975, 323),
),
en=None,
)
PLUS = ButtonWrapper(
name='PLUS',
jp=Button(
file='./assets/jp/stage/sweep/PLUS.png',
area=(995, 278, 1034, 322),
search=(975, 258, 1054, 342),
color=(233, 243, 246),
button=(995, 278, 1034, 322),
),
en=None,
)
SKIP_OK_LOWER = ButtonWrapper(
name='SKIP_OK_LOWER',
jp=Button(
file='./assets/jp/stage/sweep/SKIP_OK_LOWER.png',
area=(541, 551, 740, 616),
search=(521, 531, 760, 636),
color=(112, 212, 247),
button=(541, 551, 740, 616),
),
en=None,
)
SKIP_OK_UPPER = ButtonWrapper(
name='SKIP_OK_UPPER',
jp=Button(
file='./assets/jp/stage/sweep/SKIP_OK_UPPER.png',
area=(542, 474, 738, 545),
search=(522, 454, 758, 565),
color=(112, 212, 248),
button=(542, 474, 738, 545),
),
en=None,
)
SKIP_SKIP = ButtonWrapper(
name='SKIP_SKIP',
jp=Button(
file='./assets/jp/stage/sweep/SKIP_SKIP.png',
area=(545, 475, 736, 540),
search=(525, 455, 756, 560),
color=(110, 207, 243),
button=(545, 475, 736, 540),
),
en=None,
)
SWEEP = ButtonWrapper(
name='SWEEP',
jp=Button(
file='./assets/jp/stage/sweep/SWEEP.png',
area=(796, 385, 1067, 427),
search=(776, 365, 1087, 447),
color=(109, 202, 235),
button=(796, 385, 1067, 427),
),
en=None,
)
SWEEP_CONFIRM = ButtonWrapper(
name='SWEEP_CONFIRM',
jp=Button(
file='./assets/jp/stage/sweep/SWEEP_CONFIRM.png',
area=(611, 147, 669, 177),
search=(591, 127, 689, 197),
color=(143, 156, 170),
button=(664, 470, 871, 534),
),
en=None,
)

View File

@ -2,11 +2,12 @@ import cv2
import numpy as np
from module.base.base import ModuleBase
from module.base.button import ButtonWrapper, ClickButton
from module.base.button import ClickButton, match_template
from module.base.timer import Timer
from module.base.utils import area_pad, area_size, area_offset, random_rectangle_vector_opted
from module.logger import logger
from module.ocr.ocr import Ocr
from tasks.stage.assets.assets_stage_list import *
class StageList:
@ -15,17 +16,19 @@ class StageList:
def __init__(
self,
name,
area_stage: ButtonWrapper,
area_index: ButtonWrapper,
area_item: ButtonWrapper,
button_enter: ButtonWrapper,
button_list: ButtonWrapper = None,
button_index: ButtonWrapper = None,
button_item: ButtonWrapper = None,
button_enter: ButtonWrapper = None,
button_stars: ButtonWrapper = None,
drag_direction: str = "down"
):
self.name = name
self.stage = area_stage
self.index_ocr = Ocr(area_index, lang='en')
self.stage_item = area_item.button
self.enter = button_enter
self.stage = button_list if button_list else STAGE_LIST
self.index_ocr = Ocr(button_index if button_index else OCR_INDEX, lang='en')
self.stage_item = (button_item if button_item else STAGE_ITEM).button
self.enter = button_enter if button_enter else STAGE_ENTER
self.sweepable = button_stars if button_stars else STAGE_STARS
self.drag_direction = drag_direction
self.current_index_min = 1
@ -43,7 +46,8 @@ class StageList:
def __hash__(self):
return hash(self.name)
def _get_indexes(self) -> list[int]:
@property
def _indexes(self) -> list[int]:
return list(map(lambda x: int(x.ocr_text), self.current_indexes))
def load_stage_indexes(self, main: ModuleBase):
@ -53,7 +57,7 @@ class StageList:
if not self.current_indexes:
logger.warning(f'No valid index in {self.index_ocr.name}')
return
indexes = self._get_indexes()
indexes = self._indexes
self.current_index_min = min(indexes)
self.current_index_max = max(indexes)
@ -119,7 +123,7 @@ class StageList:
timeout=Timer(1.5, 5)
)
indexes = self._get_indexes()
indexes = self._indexes
if indexes and last_indexes == set(indexes):
logger.warning(f'No more index {index}')
return False
@ -129,20 +133,28 @@ class StageList:
@staticmethod
def _match_clickable_points(image, template, threshold=0.85):
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
template = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY)
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
loc = np.where(res >= threshold)
return [point for point in zip(*loc[::-1])]
def is_sweepable(self, image, main: ModuleBase, skip_first_screenshot=True) -> bool:
if not skip_first_screenshot:
main.device.screenshot()
return match_template(image, self.sweepable.matched_button.image)
def select_index_enter(
self,
index: int,
main: ModuleBase,
insight: bool = True,
sweepable: bool = True,
offset: tuple[int, int] = (-20, -15),
skip_first_screenshot: bool = True,
interval: int = 5
interval: int = 2
) -> bool:
if insight and not self.insight_index(index, main, skip_first_screenshot):
return False
@ -164,9 +176,14 @@ class StageList:
logger.warning(f'No index {index} in {self.index_ocr.name}')
return False
stage_item_box = area_pad((0, 0, *area_size(self.stage_item)))
stage_item_box = area_pad((*offset, *area_size(self.stage_item)))
search_box = area_offset(stage_item_box, index_box.box[:2])
search_image = main.image_crop(search_box)
if sweepable and not self.is_sweepable(search_image, main, skip_first_screenshot):
logger.warning(f'Index {index} is not sweepable')
return False
points = self._match_clickable_points(search_image, self.enter.matched_button.image)
if not points:

254
tasks/stage/sweep.py Normal file
View File

@ -0,0 +1,254 @@
from module.base.base import ModuleBase
from module.base.timer import Timer
from module.logger import logger
from module.ocr.ocr import Digit
from enum import Enum
from tasks.stage.assets.assets_stage_sweep import *
class SweepStatus(Enum):
SELECT = 1
START = 2
CONFIRM = 3
SKIP = 4
END = 5
FINISH = 6
class StageSweep:
def __init__(
self,
name: str,
sweep_num: int,
max_sweep: int,
):
self.name = name
self.sweep_num = sweep_num
self.check: ButtonWrapper = None
self.num: Digit = None
self.plus: ButtonWrapper = None
self.minus: ButtonWrapper = None
self.max: ButtonWrapper = None
self.min: ButtonWrapper = None
self.sweep: ButtonWrapper = None
self.sweep_confirm: ButtonWrapper = None
self.enter: ButtonWrapper = None
self.exit: ButtonWrapper = None
self.skip_skip: ButtonWrapper = None
self.skip_ok_upper: ButtonWrapper = None
self.skip_ok_lower: ButtonWrapper = None
self.set_button()
self.min_sweep = 1
self.max_sweep = max_sweep
self.current_sweep = 0
self.sweep_method = None
self.set_mode()
def __str__(self):
return f'StageSweep({self.name})'
__repr__ = __str__
def __eq__(self, other):
return str(self) == str(other)
def __hash__(self):
return hash(self.name)
def set_button(
self,
button_check: ButtonWrapper = None,
button_num: ButtonWrapper = None,
button_plus: ButtonWrapper = None,
button_minus: ButtonWrapper = None,
button_max: ButtonWrapper = None,
button_min: ButtonWrapper = None,
button_sweep: ButtonWrapper = None,
button_sweep_confirm: ButtonWrapper = None,
button_enter: ButtonWrapper = None,
button_exit: ButtonWrapper = None,
button_skip_skip: ButtonWrapper = None,
button_skip_ok_upper: ButtonWrapper = None,
button_skip_ok_lower: ButtonWrapper = None,
):
self.check = button_check if button_check else CHECK_SWEEP
self.num = Digit(button_num if button_num else OCR_NUM)
self.plus = button_plus if button_plus else PLUS
self.minus = button_minus if button_minus else MINUS
self.max = button_max if button_max else MAX
self.min = button_min if button_min else MIN
self.sweep = button_sweep if button_sweep else SWEEP
self.sweep_confirm = button_sweep_confirm if button_sweep_confirm else SWEEP_CONFIRM
self.enter = button_enter if button_enter else ENTER
self.exit = button_exit if button_exit else EXIT
self.skip_skip = button_skip_skip if button_skip_skip else SKIP_SKIP
self.skip_ok_upper = button_skip_ok_upper if button_skip_ok_upper else SKIP_OK_UPPER
self.skip_ok_lower = button_skip_ok_lower if button_skip_ok_lower else SKIP_OK_LOWER
def set_mode(self, mode: str = None):
if mode is None:
match self.sweep_num:
case 0:
self.sweep_method = self.set_sweep_min
case -1:
self.sweep_method = self.set_sweep_max
case x if x > 0:
self.sweep_method = self.set_sweep_num
case _:
logger.warning(f'Invalid sweep num: {self.sweep_num}')
return
match mode:
case 'max':
self.sweep_method = self.set_sweep_max
case 'min':
self.sweep_method = self.set_sweep_min
case _:
logger.warning(f'Invalid sweep mode: {mode}')
def check_sweep(self, main: ModuleBase):
return main.appear(self.check)
def check_skip(self, main: ModuleBase):
return main.appear(self.skip_skip) or main.appear(self.skip_ok_upper) or main.appear(self.skip_ok_lower)
def load_sweep_num(self, main: ModuleBase):
while 1:
main.device.screenshot()
ocr_result = self.num.detect_and_ocr(main.device.image)
if not ocr_result:
logger.warning(f'No valid num in {self.num.name}')
continue
if len(ocr_result) == 1:
self.current_sweep = int(ocr_result[0].ocr_text)
return
def set_sweep_num(self, main: ModuleBase, skip_first_screenshot=True) -> bool:
num = self.sweep_num
if num < self.min_sweep or num > self.max_sweep:
logger.warning(f'Invalid sweep num: {num}')
return False
logger.info(f'Set sweep num: {num}')
retry = Timer(1, 2)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_sweep_num(main)
if self.current_sweep == num:
logger.info(f'Sweep num reaches {num}')
return True
elif self.current_sweep == 0:
logger.info(f'Current sweep num is 0')
return False
if retry.reached_and_reset():
diff = num - self.current_sweep
button = self.plus if diff > 0 else self.minus
main.device.multi_click(button, abs(diff), interval=(0.2, 0.3))
def set_sweep_max(self, main: ModuleBase, skip_first_screenshot=True):
logger.info(f'Set sweep max: {self.max_sweep}')
retry = Timer(1, 2)
count = 0
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_sweep_num(main)
if self.current_sweep == self.max_sweep:
logger.info(f'Sweep max reaches {self.max_sweep}')
return True
elif count == 1 and self.current_sweep != 1:
logger.info("Set sweep max")
return True
elif self.current_sweep == 0:
logger.info(f'Current sweep num is 0')
return False
if retry.reached_and_reset():
main.click_with_interval(self.max, interval=0)
count += 1
continue
if count > 2:
logger.info("Set sweep max")
return True
def set_sweep_min(self, main: ModuleBase, skip_first_screenshot=True):
logger.info(f'Set sweep min: {self.min_sweep}')
retry = Timer(1, 2)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_sweep_num(main)
if self.current_sweep == self.min_sweep:
logger.info(f'Sweep min reaches {self.min_sweep}')
return True
elif self.current_sweep == 0:
logger.info(f'Current sweep num is 0')
return False
if retry.reached_and_reset():
main.click_with_interval(self.min, interval=0)
def do_sweep(self, main: ModuleBase, skip_first_screenshot=True) -> bool:
timer = Timer(0.5, 1)
timer_stable = Timer(0.5, 1).start()
status = SweepStatus.SELECT
while 1:
if not timer_stable.reached():
continue
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
if timer.reached_and_reset():
logger.attr("Status", status)
match status:
case SweepStatus.SELECT:
if self.sweep_method(main, skip_first_screenshot):
status = SweepStatus.START
else:
return False
case SweepStatus.START:
main.appear_then_click(self.sweep, interval=1)
if main.appear(self.sweep_confirm):
status = SweepStatus.CONFIRM
case SweepStatus.CONFIRM:
main.appear_then_click(self.sweep_confirm, interval=1)
if self.check_skip(main):
status = SweepStatus.SKIP
case SweepStatus.SKIP:
main.appear_then_click(self.skip_skip)
main.appear_then_click(self.skip_ok_upper)
main.appear_then_click(self.skip_ok_lower)
if self.check_sweep(main):
status = SweepStatus.END
case SweepStatus.END:
main.appear_then_click(self.exit, interval=1)
if not main.appear(self.check):
status = SweepStatus.FINISH
case SweepStatus.FINISH:
pass
case _:
logger.warning(f'Invalid status: {status}')
return False
if status == SweepStatus.FINISH:
logger.info(f'Sweep finish')
return True