Merge pull request #4419 from LmeSzinc/dev

Bug fix
This commit is contained in:
LmeSzinc 2024-12-09 23:41:13 +08:00 committed by GitHub
commit 80817640d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 196 additions and 127 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -124,14 +124,14 @@ class ModuleBase:
return button
def appear(self, button, offset=0, interval=0, threshold=None):
def appear(self, button, offset=0, interval=0, similarity=0.85, threshold=30):
"""
Args:
button (Button, Template, HierarchyButton, str):
offset (bool, int):
interval (int, float): interval between two active events.
threshold (int, float): 0 to 1 if use offset, bigger means more similar,
0 to 255 if not use offset, smaller means more similar
similarity (int, float): 0 to 1.
threshold (int, float): 0 to 255 if not use offset, smaller means more similar
Returns:
bool:
@ -167,20 +167,51 @@ class ModuleBase:
elif offset:
if isinstance(offset, bool):
offset = self.config.BUTTON_OFFSET
appear = button.match(self.device.image, offset=offset,
threshold=self.config.BUTTON_MATCH_SIMILARITY if threshold is None else threshold)
appear = button.match(self.device.image, offset=offset, similarity=similarity)
else:
appear = button.appear_on(self.device.image,
threshold=self.config.COLOR_SIMILAR_THRESHOLD if threshold is None else threshold)
appear = button.appear_on(self.device.image, threshold=threshold)
if appear and interval:
self.interval_timer[button.name].reset()
return appear
def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, threshold=None):
def match_template_color(self, button, offset=(20, 20), interval=0, similarity=0.85, threshold=30):
"""
Args:
button (Button):
offset (bool, int):
interval (int, float): interval between two active events.
similarity (int, float): 0 to 1.
threshold (int, float): 0 to 255 if not use offset, smaller means more similar
Returns:
bool:
"""
button = self.ensure_button(button)
appear = self.appear(button, offset=offset, interval=interval, threshold=threshold)
self.device.stuck_record_add(button)
if interval:
if button.name in self.interval_timer:
if self.interval_timer[button.name].limit != interval:
self.interval_timer[button.name] = Timer(interval)
else:
self.interval_timer[button.name] = Timer(interval)
if not self.interval_timer[button.name].reached():
return False
appear = button.match_template_color(
self.device.image, offset=offset, similarity=similarity, threshold=threshold)
if appear and interval:
self.interval_timer[button.name].reset()
return appear
def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, similarity=0.85,
threshold=30):
button = self.ensure_button(button)
appear = self.appear(button, offset=offset, interval=interval, similarity=similarity, threshold=threshold)
if appear:
if screenshot:
self.device.sleep(self.config.WAIT_BEFORE_SAVING_SCREEN_SHOT)

View File

@ -198,13 +198,13 @@ class Button(Resource):
self._match_binary_init = False
self._match_luma_init = False
def match(self, image, offset=30, threshold=0.85):
def match(self, image, offset=30, similarity=0.85):
"""Detects button by template matching. To Some button, its location may not be static.
Args:
image: Screenshot.
offset (int, tuple): Detection area offset.
threshold (float): 0-1. Similarity.
similarity (float): 0-1. Similarity.
Returns:
bool.
@ -223,25 +223,25 @@ class Button(Resource):
if self.is_gif:
for template in self.image:
res = cv2.matchTemplate(template, image, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = area_offset(self._button, offset[:2] + np.array(point))
if similarity > threshold:
if sim > similarity:
return True
return False
else:
res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = area_offset(self._button, offset[:2] + np.array(point))
return similarity > threshold
return sim > similarity
def match_binary(self, image, offset=30, threshold=0.85):
def match_binary(self, image, offset=30, similarity=0.85):
"""Detects button by template matching. To Some button, its location may not be static.
This method will apply template matching under binarization.
Args:
image: Screenshot.
offset (int, tuple): Detection area offset.
threshold (float): 0-1. Similarity.
similarity (float): 0-1. Similarity.
Returns:
bool.
@ -266,9 +266,9 @@ class Button(Resource):
_, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# template matching
res = cv2.matchTemplate(template, image_binary, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = area_offset(self._button, offset[:2] + np.array(point))
if similarity > threshold:
if sim > similarity:
return True
return False
else:
@ -278,18 +278,18 @@ class Button(Resource):
_, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# template matching
res = cv2.matchTemplate(self.image_binary, image_binary, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = area_offset(self._button, offset[:2] + np.array(point))
return similarity > threshold
return sim > similarity
def match_luma(self, image, offset=30, threshold=0.85):
def match_luma(self, image, offset=30, similarity=0.85):
"""
Detects button by template matching under Y channel (Luminance)
Args:
image: Screenshot.
offset (int, tuple): Detection area offset.
threshold (float): 0-1. Similarity.
similarity (float): 0-1. Similarity.
Returns:
bool.
@ -310,29 +310,37 @@ class Button(Resource):
image_luma = rgb2luma(image)
for template in self.image_luma:
res = cv2.matchTemplate(template, image_luma, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = area_offset(self._button, offset[:2] + np.array(point))
if similarity > threshold:
if sim > similarity:
return True
else:
image_luma = rgb2luma(image)
res = cv2.matchTemplate(self.image_luma, image_luma, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = area_offset(self._button, offset[:2] + np.array(point))
return similarity > threshold
return sim > similarity
def match_appear_on(self, image, threshold=30):
def match_template_color(self, image, offset=(20, 20), similarity=0.85, threshold=30):
"""
Template match first, color match then
Args:
image: Screenshot.
threshold: Default to 10.
offset (int, tuple): Detection area offset.
similarity (float): 0-1.
threshold (int): Default to 30.
Returns:
bool:
bool.
"""
diff = np.subtract(self.button, self._button)[:2]
area = area_offset(self.area, offset=diff)
return color_similar(color1=get_color(image, area), color2=self.color, threshold=threshold)
if self.match(image, offset=offset, similarity=similarity):
diff = np.subtract(self.button, self._button)[:2]
area = area_offset(self.area, offset=diff)
color = get_color(image, area)
return color_similar(color1=color, color2=self.color, threshold=threshold)
else:
return False
def crop(self, area, image=None, name=None):
"""

View File

@ -8,7 +8,7 @@ COMMISSION_ADVICE = Button(area={'cn': (871, 322, 999, 383), 'en': (871, 328, 10
COMMISSION_DAILY = Button(area={'cn': (35, 132, 67, 186), 'en': (30, 126, 75, 188), 'jp': (17, 168, 82, 185), 'tw': (35, 132, 67, 186)}, color={'cn': (208, 172, 118), 'en': (170, 132, 92), 'jp': (148, 115, 76), 'tw': (208, 171, 119)}, button={'cn': (35, 132, 67, 186), 'en': (30, 126, 75, 188), 'jp': (17, 168, 82, 185), 'tw': (35, 132, 67, 186)}, file={'cn': './assets/cn/commission/COMMISSION_DAILY.png', 'en': './assets/en/commission/COMMISSION_DAILY.png', 'jp': './assets/jp/commission/COMMISSION_DAILY.png', 'tw': './assets/tw/commission/COMMISSION_DAILY.png'})
COMMISSION_HAS_PENDING = Button(area={'cn': (320, 288, 380, 338), 'en': (320, 288, 380, 338), 'jp': (320, 288, 380, 338), 'tw': (320, 288, 380, 338)}, color={'cn': (121, 113, 152), 'en': (121, 113, 152), 'jp': (121, 113, 152), 'tw': (121, 113, 152)}, button={'cn': (320, 288, 380, 338), 'en': (320, 288, 380, 338), 'jp': (320, 288, 380, 338), 'tw': (320, 288, 380, 338)}, file={'cn': './assets/cn/commission/COMMISSION_HAS_PENDING.png', 'en': './assets/en/commission/COMMISSION_HAS_PENDING.png', 'jp': './assets/jp/commission/COMMISSION_HAS_PENDING.png', 'tw': './assets/tw/commission/COMMISSION_HAS_PENDING.png'})
COMMISSION_SCROLL_AREA = Button(area={'cn': (1254, 77, 1261, 676), 'en': (1254, 77, 1261, 676), 'jp': (1254, 77, 1261, 676), 'tw': (1254, 77, 1261, 676)}, color={'cn': (213, 183, 66), 'en': (213, 183, 66), 'jp': (213, 183, 66), 'tw': (213, 183, 66)}, button={'cn': (1254, 77, 1261, 676), 'en': (1254, 77, 1261, 676), 'jp': (1254, 77, 1261, 676), 'tw': (1254, 77, 1261, 676)}, file={'cn': './assets/cn/commission/COMMISSION_SCROLL_AREA.png', 'en': './assets/en/commission/COMMISSION_SCROLL_AREA.png', 'jp': './assets/jp/commission/COMMISSION_SCROLL_AREA.png', 'tw': './assets/tw/commission/COMMISSION_SCROLL_AREA.png'})
COMMISSION_START = Button(area={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1062, 342, 1125, 374), 'tw': (1027, 326, 1157, 389)}, color={'cn': (229, 175, 113), 'en': (236, 197, 150), 'jp': (237, 201, 153), 'tw': (231, 180, 120)}, button={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1028, 336, 1157, 398), 'tw': (1027, 326, 1157, 389)}, file={'cn': './assets/cn/commission/COMMISSION_START.png', 'en': './assets/en/commission/COMMISSION_START.png', 'jp': './assets/jp/commission/COMMISSION_START.png', 'tw': './assets/tw/commission/COMMISSION_START.png'})
COMMISSION_START = Button(area={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1033, 340, 1153, 376), 'tw': (1027, 326, 1157, 389)}, color={'cn': (229, 175, 113), 'en': (236, 197, 150), 'jp': (231, 184, 121), 'tw': (231, 180, 120)}, button={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1033, 340, 1153, 376), 'tw': (1027, 326, 1157, 389)}, file={'cn': './assets/cn/commission/COMMISSION_START.png', 'en': './assets/en/commission/COMMISSION_START.png', 'jp': './assets/jp/commission/COMMISSION_START.png', 'tw': './assets/tw/commission/COMMISSION_START.png'})
COMMISSION_URGENT = Button(area={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 283), 'jp': (34, 266, 68, 279), 'tw': (35, 229, 69, 280)}, color={'cn': (215, 188, 124), 'en': (169, 138, 95), 'jp': (216, 190, 111), 'tw': (213, 186, 123)}, button={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 283), 'jp': (34, 266, 68, 279), 'tw': (35, 229, 69, 280)}, file={'cn': './assets/cn/commission/COMMISSION_URGENT.png', 'en': './assets/en/commission/COMMISSION_URGENT.png', 'jp': './assets/jp/commission/COMMISSION_URGENT.png', 'tw': './assets/tw/commission/COMMISSION_URGENT.png'})
EXP_INFO_S_REWARD = Button(area={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, color={'cn': (233, 241, 127), 'en': (89, 115, 159), 'jp': (233, 241, 127), 'tw': (233, 241, 127)}, button={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, file={'cn': './assets/cn/commission/EXP_INFO_S_REWARD.png', 'en': './assets/en/commission/EXP_INFO_S_REWARD.png', 'jp': './assets/jp/commission/EXP_INFO_S_REWARD.png', 'tw': './assets/tw/commission/EXP_INFO_S_REWARD.png'})
REWARD_1 = Button(area={'cn': (383, 285, 503, 297), 'en': (403, 274, 504, 290), 'jp': (432, 273, 476, 294), 'tw': (383, 285, 503, 297)}, color={'cn': (238, 168, 81), 'en': (241, 198, 145), 'jp': (241, 188, 122), 'tw': (238, 168, 81)}, button={'cn': (383, 285, 503, 297), 'en': (392, 262, 515, 303), 'jp': (393, 262, 514, 303), 'tw': (383, 285, 503, 297)}, file={'cn': './assets/cn/commission/REWARD_1.png', 'en': './assets/en/commission/REWARD_1.png', 'jp': './assets/jp/commission/REWARD_1.png', 'tw': './assets/tw/commission/REWARD_1.png'})

View File

@ -363,7 +363,8 @@ class RewardCommission(UI, InfoHandler):
raise GameStuckError('Triggered commission list flashing bug')
# Click
if self.appear_then_click(COMMISSION_START, offset=(5, 20), interval=7):
if self.match_template_color(COMMISSION_START, offset=(5, 20), interval=7):
self.device.click(COMMISSION_START)
self.interval_reset(COMMISSION_ADVICE)
comm_timer.reset()
continue

View File

@ -39,9 +39,7 @@ class ManualConfig:
"""
module.base
"""
COLOR_SIMILAR_THRESHOLD = 10
BUTTON_OFFSET = 30
BUTTON_MATCH_SIMILARITY = 0.85
WAIT_BEFORE_SAVING_SCREEN_SHOT = 1
"""

View File

@ -183,8 +183,7 @@ class BuyFurniture(UI):
False if Failed buy
"""
self.enter_first_furniture_details_page()
if self.appear(DORM_FURNITURE_COUNTDOWN, offset=(20, 20)) \
and DORM_FURNITURE_COUNTDOWN.match_appear_on(self.device.image):
if self.match_template_color(DORM_FURNITURE_COUNTDOWN, offset=(20, 20)):
logger.info("There is a time-limited furniture available for buy")
if self.buy_furniture_once(self.config.BuyFurniture_BuyOption):

View File

@ -6,6 +6,7 @@ from module.logger import logger
from module.ui.assets import BATTLE_PASS_CHECK, REWARD_GOTO_BATTLE_PASS
from module.ui.page import page_reward
from module.ui.ui import UI
from module.ui_white.assets import POPUP_CONFIRM_WHITE_BATTLEPASS
class BattlePass(Combat, UI):
@ -72,8 +73,7 @@ class BattlePass(Combat, UI):
if self.appear_then_click(REWARD_RECEIVE, offset=(20, 20), interval=3):
confirm_timer.reset()
continue
if self.appear(REWARD_RECEIVE_SP, offset=(20, 20), interval=3) \
and REWARD_RECEIVE_SP.match_appear_on(self.device.image, threshold=15):
if self.match_template_color(REWARD_RECEIVE_SP, offset=(20, 20), interval=3, threshold=15):
self.device.click(REWARD_RECEIVE_SP)
confirm_timer.reset()
continue
@ -83,6 +83,10 @@ class BattlePass(Combat, UI):
if self.handle_battle_pass_popup():
confirm_timer.reset()
continue
if self.config.SERVER == 'cn':
if self.appear_then_click(POPUP_CONFIRM_WHITE_BATTLEPASS, offset=(20, 20), interval=3):
confirm_timer.reset()
continue
if self.handle_popup_confirm('BATTLE_PASS'):
# Lock new META ships
confirm_timer.reset()

View File

@ -50,7 +50,7 @@ class LoginHandler(UI):
confirm_timer.reset()
# Login
if self.appear(LOGIN_CHECK, offset=(30, 30), interval=5) and LOGIN_CHECK.match_appear_on(self.device.image):
if self.match_template_color(LOGIN_CHECK, offset=(30, 30), interval=5):
self.device.click(LOGIN_CHECK)
if not login_success:
logger.info('Login success')

View File

@ -217,8 +217,7 @@ class StrategyHandler(InfoHandler):
in: STRATEGY_OPENED
out: STRATEGY_OPENED
"""
if (self.appear(MOB_MOVE_ENTER, offset=MOB_MOVE_OFFSET)
and MOB_MOVE_ENTER.match_appear_on(self.device.image)):
if self.match_template_color(MOB_MOVE_ENTER, offset=MOB_MOVE_OFFSET):
return True
else:
return False

View File

@ -269,8 +269,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand
return True
if mode == 'normal':
if self.appear(MAP_MODE_SWITCH_NORMAL, offset=(20, 20)) \
and MAP_MODE_SWITCH_NORMAL.match_appear_on(self.device.image):
if self.match_template_color(MAP_MODE_SWITCH_NORMAL, offset=(20, 20)):
logger.attr('MAP_MODE_SWITCH', 'normal')
return True
elif self.appear(MAP_MODE_SWITCH_HARD, offset=(20, 20), interval=2):
@ -281,8 +280,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand
else:
return False
elif mode == 'hard':
if self.appear(MAP_MODE_SWITCH_HARD, offset=(20, 20)) \
and MAP_MODE_SWITCH_HARD.match_appear_on(self.device.image):
if self.match_template_color(MAP_MODE_SWITCH_HARD, offset=(20, 20)):
logger.attr('MAP_MODE_SWITCH', 'hard')
return True
if self.appear(MAP_MODE_SWITCH_NORMAL, offset=(20, 20), interval=2):

View File

@ -332,11 +332,11 @@ class GridPredictor:
color = cv2.mean(crop(mask.image, area=np.rint(area).astype(int), copy=False))
return color[0] > 235
def is_similar_to(self, grid, threshold=0.9):
def is_similar_to(self, grid, similarity=0.9):
"""
Args:
grid (GridPredictor): Another Grid instance.
threshold (float): 0 to 1.
similarity (float): 0 to 1.
Returns:
bool: If current grid is similar to another.
@ -346,5 +346,5 @@ class GridPredictor:
piece_1 = self._image_similar_piece
piece_2 = grid._image_similar_full
res = cv2.matchTemplate(piece_2, piece_1, cv2.TM_CCOEFF_NORMED)
_, similarity, _, point = cv2.minMaxLoc(res)
return similarity > threshold
_, sim, _, point = cv2.minMaxLoc(res)
return sim > similarity

View File

@ -69,8 +69,7 @@ class MeowfficerBase(UI):
self.device.screenshot()
# End
if self.appear(MEOWFFICER_CHECK, offset=(20, 20)) \
and MEOWFFICER_CHECK.match_appear_on(self.device.image):
if self.match_template_color(MEOWFFICER_CHECK, offset=(20, 20)):
break
else:
if click_timer.reached():

View File

@ -99,8 +99,7 @@ class MeowfficerBuy(MeowfficerBase):
continue
# End
if self.appear(MEOWFFICER_BUY_ENTER, offset=(20, 20)) \
and MEOWFFICER_BUY_ENTER.match_appear_on(self.device.image):
if self.match_template_color(MEOWFFICER_BUY_ENTER, offset=(20, 20)):
break
def meow_buy(self) -> bool:

View File

@ -75,8 +75,7 @@ class MeowfficerCollect(MeowfficerBase):
Returns:
bool
"""
if self.appear(MEOWFFICER_GET_CHECK, offset=(40, 40)) and MEOWFFICER_GET_CHECK.match_appear_on(
self.device.image):
if self.match_template_color(MEOWFFICER_GET_CHECK, offset=(40, 40)):
return True
if self.appear(MEOWFFICER_TRAIN_START, offset=(20, 20)):

View File

@ -64,8 +64,7 @@ class BeaconReward(Combat, UI):
else:
self.device.screenshot()
if self.appear(REWARD_RECEIVE, offset=(20, 20), interval=3) and REWARD_RECEIVE.match_appear_on(
self.device.image):
if self.match_template_color(REWARD_RECEIVE, offset=(20, 20), interval=3):
self.device.click(REWARD_RECEIVE)
confirm_timer.reset()
continue
@ -117,7 +116,7 @@ class DossierReward(Combat, UI):
in: dossier meta page
"""
self.device.screenshot()
if self.appear(DOSSIER_REWARD_RECEIVE, offset=(-40, 10, -10, 40), threshold=0.7):
if self.appear(DOSSIER_REWARD_RECEIVE, offset=(-40, 10, -10, 40), similarity=0.7):
logger.info('Found dossier reward red dot')
return True
else:
@ -166,8 +165,7 @@ class DossierReward(Combat, UI):
else:
self.device.screenshot()
if self.appear(DOSSIER_REWARD_RECEIVE, offset=(20, 20), interval=3) and DOSSIER_REWARD_RECEIVE.match_appear_on(
self.device.image):
if self.match_template_color(DOSSIER_REWARD_RECEIVE, offset=(20, 20), interval=3):
self.device.click(DOSSIER_REWARD_RECEIVE)
confirm_timer.reset()
continue

View File

@ -369,7 +369,8 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh):
# Arrive
# Check colors, because screen goes black when something is unlocking.
if self.is_in_map() and IN_MAP.match_appear_on(self.device.image):
# A direct use of IN_MAP, basically `self.is_in_map() and IN_MAP.match_template_color()`
if self.match_template_color(IN_MAP, offset=(200, 5)):
self.update_os()
current = self.view.backend.homo_loca
logger.attr('homo_loca', current)

View File

@ -29,7 +29,7 @@ class FleetSelector:
int: Index of current fleet, 1 to 4. return 0 if unrecognized.
"""
for index, button in enumerate([FLEET_1, FLEET_2, FLEET_3, FLEET_4]):
if self.main.appear(button, offset=(20, 20), threshold=0.75):
if self.main.appear(button, offset=(20, 20), similarity=0.75):
return index + 1
logger.info('Unknown OpSi fleet')

View File

@ -231,12 +231,10 @@ class OpsiAshBeacon(Meta):
else:
self.device.screenshot()
if self.appear(META_INNER_PAGE_DAMAGE, offset=(20, 20)) \
and META_INNER_PAGE_DAMAGE.match_appear_on(self.device.image):
if self.match_template_color(META_INNER_PAGE_DAMAGE, offset=(20, 20)):
logger.info('Already in meta damage page')
break
if self.appear(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20)) \
and META_INNER_PAGE_NOT_DAMAGE.match_appear_on(self.device.image):
if self.match_template_color(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20)):
logger.info('In meta details page, should switch to damage page')
self.appear_then_click(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20), interval=2)
continue

View File

@ -106,8 +106,7 @@ class ActionPointHandler(UI, MapEventHandler):
return self.appear(ACTION_POINT_USE, offset=(20, 20))
def is_current_ap_visible(self):
return self.appear(CURRENT_AP_CHECK, offset=(40, 5)) \
and CURRENT_AP_CHECK.match_appear_on(self.device.image, threshold=15)
return self.match_template_color(CURRENT_AP_CHECK, offset=(40, 5), threshold=15)
def action_point_use(self, skip_first_screenshot=True):
prev = self._action_point_current

View File

@ -10,7 +10,7 @@ class EnemySearchingHandler(EnemySearchingHandler_):
def is_in_map(self):
if IN_MAP.match_luma(self.device.image, offset=(200, 5)):
return True
if self.appear(MAP_GOTO_GLOBE_FOG, offset=(5, 5)) and MAP_GOTO_GLOBE_FOG.match_appear_on(self.device.image):
if self.match_template_color(MAP_GOTO_GLOBE_FOG, offset=(5, 5)):
return True
return False

View File

@ -1,12 +1,13 @@
from module.base.timer import Timer
from module.combat.assets import *
from module.exception import CampaignEnd
from module.handler.assets import *
from module.handler.assets import POPUP_CANCEL, POPUP_CONFIRM
from module.logger import logger
from module.os.assets import GLOBE_GOTO_MAP
from module.os_handler.assets import *
from module.os_handler.enemy_searching import EnemySearchingHandler
from module.statistics.azurstats import DropImage
from module.ui.assets import BACK_ARROW
from module.ui.switch import Switch
@ -208,7 +209,6 @@ class MapEventHandler(EnemySearchingHandler):
if drop:
drop.handle_add(main=self, before=4)
self.device.click(AUTO_SEARCH_REWARD)
self.clicked = True
self.interval_reset([
AUTO_SEARCH_REWARD,
AUTO_SEARCH_OS_MAP_OPTION_ON,
@ -225,6 +225,14 @@ class MapEventHandler(EnemySearchingHandler):
# because of duplicated clicks and clicks to places outside the map
confirm_timer.reset()
continue
# Donno why but it just entered storage, exit it anyway
# Equivalent to is_in_storage, but can't inherit StorageHandler here
# STORAGE_CHECK is a duplicate name, this is the os_handler/STORAGE_CHECK, not handler/STORAGE_CHECK
if self.appear(STORAGE_CHECK, offset=(20, 20), interval=5):
logger.info(f'{STORAGE_CHECK} -> {BACK_ARROW}')
self.device.click(BACK_ARROW)
confirm_timer.reset()
continue
# End
if self.is_in_map():
@ -244,14 +252,12 @@ class MapEventHandler(EnemySearchingHandler):
Returns:
bool: If clicked.
"""
if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)) \
and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image):
if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)):
if self.info_bar_count() >= 2:
self.device.screenshot_interval_set()
self.os_auto_search_quit(drop=drop)
raise CampaignEnd
if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120)) \
and AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED.match_appear_on(self.device.image):
if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120)):
if self.info_bar_count() >= 2:
self.device.screenshot_interval_set()
self.os_auto_search_quit(drop=drop)
@ -268,20 +274,17 @@ class MapEventHandler(EnemySearchingHandler):
if enable is None:
pass
elif enable:
if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120), interval=3) \
and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image):
if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120), interval=3):
self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF)
self.interval_reset(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED)
return True
# Game client bugged sometimes, AUTO_SEARCH_OS_MAP_OPTION_OFF grayed out but still functional
if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120), interval=3) \
and AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED.match_appear_on(self.device.image):
if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120), interval=3):
self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED)
self.interval_reset(AUTO_SEARCH_OS_MAP_OPTION_OFF)
return True
else:
if self.appear(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120), interval=3) \
and AUTO_SEARCH_OS_MAP_OPTION_ON.match_appear_on(self.device.image):
if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120), interval=3):
self.device.click(AUTO_SEARCH_OS_MAP_OPTION_ON)
return True

View File

@ -81,15 +81,13 @@ class MissionHandler(GlobeOperation, ZoneManager):
# End
if self.is_in_os_mission() \
and not self.appear(MISSION_FINISH, offset=(20, 20)) \
and not (self.appear(MISSION_CHECKOUT, offset=(20, 20))
and MISSION_CHECKOUT.match_appear_on(self.device.image)):
and not self.match_template_color(MISSION_CHECKOUT, offset=(20, 20)):
# No mission found, wait to confirm. Missions might not be loaded so fast.
if confirm_timer.reached():
logger.info('No OS mission found.')
break
elif self.is_in_os_mission() \
and (self.appear(MISSION_CHECKOUT, offset=(20, 20))
and MISSION_CHECKOUT.match_appear_on(self.device.image)):
and self.match_template_color(MISSION_CHECKOUT, offset=(20, 20)):
# Found one mission.
logger.info('Found at least one OS missions.')
break
@ -119,8 +117,7 @@ class MissionHandler(GlobeOperation, ZoneManager):
logger.info('Monthly BOSS mission found, checking missions bellow it')
checkout_offset = (-20, 100, 20, 150)
if not (self.appear(MISSION_CHECKOUT, offset=checkout_offset)
and MISSION_CHECKOUT.match_appear_on(self.device.image)):
if not self.match_template_color(MISSION_CHECKOUT, offset=checkout_offset):
# If not having enough items to claim a mission,
# there will still be MISSION_CHECKOUT, but button is transparent.
# So here needs to use both template matching and color detection.

View File

@ -25,8 +25,7 @@ class StrategicSearchHandler(MapEventHandler):
continue
if self.appear(AUTO_SEARCH_REWARD, offset=(50, 50)):
continue
if self.appear(STRATEGIC_SEARCH_MAP_OPTION_OFF, offset=(20, 20), interval=2) \
and STRATEGIC_SEARCH_MAP_OPTION_OFF.match_appear_on(self.device.image):
if self.match_template_color(STRATEGIC_SEARCH_MAP_OPTION_OFF, offset=(20, 20), interval=2):
self.device.click(STRATEGIC_SEARCH_MAP_OPTION_OFF)
continue
@ -104,7 +103,7 @@ class StrategicSearchHandler(MapEventHandler):
else:
self.device.screenshot()
self.appear(STRATEGIC_SEARCH_DEVICE_CHECK, offset=(20, 200), threshold=0.7)
self.appear(STRATEGIC_SEARCH_DEVICE_CHECK, offset=(20, 200), similarity=0.7)
STRATEGIC_SEARCH_DEVICE_STOP.load_offset(STRATEGIC_SEARCH_DEVICE_CHECK)
STRATEGIC_SEARCH_DEVICE_CONTINUE.load_offset(STRATEGIC_SEARCH_DEVICE_CHECK)
@ -129,7 +128,7 @@ class StrategicSearchHandler(MapEventHandler):
else:
self.device.screenshot()
self.appear(STRATEGIC_SEARCH_SUBMIT_CHECK, offset=(20, 20), threshold=0.7)
self.appear(STRATEGIC_SEARCH_SUBMIT_CHECK, offset=(20, 20), similarity=0.7)
STRATEGIC_SEARCH_SUBMIT_OFF.load_offset(STRATEGIC_SEARCH_SUBMIT_CHECK)
STRATEGIC_SEARCH_SUBMIT_ON.load_offset(STRATEGIC_SEARCH_SUBMIT_CHECK)

View File

@ -180,14 +180,14 @@ def parse_time(string):
return timedelta(hours=result[0], minutes=result[1], seconds=result[2])
def match_template(image, template, area, offset=30, threshold=0.85):
def match_template(image, template, area, offset=30, similarity=0.85):
"""
Args:
image (np.ndarray): Screenshot
template (np.ndarray):
area (tuple): Crop area of image.
offset (int, tuple): Detection area offset.
threshold (float): 0-1. Similarity. Lower than this value will return float(0).
similarity (float): 0-1. Similarity. Lower than this value will return float(0).
Returns:
similarity (float):
"""
@ -198,8 +198,9 @@ def match_template(image, template, area, offset=30, threshold=0.85):
image = crop(image, offset + area, copy=False)
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res)
similarity = sim if sim >= threshold else 0.0
return similarity
if sim < similarity:
sim = 0.0
return sim
def get_research_series_jp_old(image):
@ -274,7 +275,7 @@ def get_research_genre_jp(image):
"""
genre = ''
for button in RESEARCH_DETAIL_GENRE:
if button.match(image, offset=(30, 20), threshold=0.9):
if button.match(image, offset=(30, 20), similarity=0.9):
# DETAIL_GENRE_H_0.name.split("_")[2] == 'H'
genre = button.name.split("_")[2]
break
@ -308,7 +309,7 @@ def get_research_cost_jp(image):
template=template,
area=DETAIL_COST.area,
offset=(10, 10),
threshold=0.8)
similarity=0.8)
if not sim:
continue
for cost in costs:
@ -343,7 +344,7 @@ def get_research_ship_jp(image):
template=load_image(template),
area=DETAIL_BLUEPRINT.area,
offset=(10, 10),
threshold=0.9)
similarity=0.9)
if sim > similarity:
similarity = sim
ship = name

View File

@ -110,12 +110,9 @@ class Retirement(Enhancement, QuickRetireSettingHandler):
timeout.reset()
# Click
if self.appear(SHIP_CONFIRM, offset=(30, 30), interval=2):
if SHIP_CONFIRM.match_appear_on(self.device.image):
self.device.click(SHIP_CONFIRM)
continue
else:
self.interval_clear(SHIP_CONFIRM)
if self.match_template_color(SHIP_CONFIRM, offset=(30, 30), interval=2):
self.device.click(SHIP_CONFIRM)
continue
if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=2):
if self.retire_keep_common_cv and not self._have_kept_cv:
self.keep_one_common_cv()
@ -259,7 +256,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler):
if selected == 0:
break
self.device.screenshot()
if not (self.appear(SHIP_CONFIRM, offset=(30, 30)) and SHIP_CONFIRM.match_appear_on(self.device.image)):
if not self.match_template_color(SHIP_CONFIRM, offset=(30, 30)):
logger.warning('No ship selected, retrying')
continue
@ -454,6 +451,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler):
"""
count = 0
RETIRE_COIN.load_color(self.device.image)
RETIRE_COIN._match_init = True
while 1:
if skip_first_screenshot:
@ -462,7 +460,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler):
self.device.screenshot()
# End
if not self.appear(RETIRE_COIN, threshold=0.97):
if not RETIRE_COIN.match(self.device.image, offset=(20, 20), similarity=0.97):
return True
if count > 3:
logger.warning('_retire_select_one failed after 3 trial')
@ -504,25 +502,56 @@ class Retirement(Enhancement, QuickRetireSettingHandler):
return None
def retirement_get_common_rarity_cv(self, skip_first_screenshot=False):
button = self.retirement_get_common_rarity_cv_in_page()
if button is not None:
return button
"""
Args:
skip_first_screenshot:
for _ in range(7):
if not RETIRE_CONFIRM_SCROLL.appear(main=self):
logger.info('Scroll bar disappeared, stop')
break
RETIRE_CONFIRM_SCROLL.next_page(main=self)
Returns:
Button: Button to click to remove ship from retire list
"""
swipe_count = 0
disappear_confirm = Timer(2, count=6)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# Try to get CV
button = self.retirement_get_common_rarity_cv_in_page()
if button is not None:
return button
# Wait scroll bar
if RETIRE_CONFIRM_SCROLL.appear(main=self):
disappear_confirm.clear()
else:
disappear_confirm.start()
if disappear_confirm.reached():
logger.warning('Scroll bar disappeared, stop')
break
else:
continue
if RETIRE_CONFIRM_SCROLL.at_bottom(main=self):
logger.info('Scroll bar reached end, stop')
break
# Swipe next page
if swipe_count >= 7:
logger.info('Reached maximum swipes to find common CV')
break
RETIRE_CONFIRM_SCROLL.next_page(main=self)
swipe_count += 1
return button
def keep_one_common_cv(self):
"""
Returns:
"""
logger.info('Keep one common CV')
button = self.retirement_get_common_rarity_cv()
if button is not None:
self._retire_select_one(button)

View File

@ -103,8 +103,7 @@ class Reward(UI):
for button in [MISSION_MULTI, MISSION_SINGLE]:
if not click_timer.reached():
continue
if self.appear(button, offset=(20, 200), interval=interval) \
and button.match_appear_on(self.device.image):
if self.match_template_color(button, offset=(20, 200), interval=interval):
self.device.click(button)
exit_timer.reset()
click_timer.reset()

View File

@ -67,7 +67,10 @@ class DropImage:
return len(self.images)
def __bool__(self):
return self.save or self.upload
# Uncomment these if stats service re-run in the future
# return self.save or self.upload
return self.save
def __enter__(self):
return self
@ -217,6 +220,8 @@ class AzurStats:
save_thread = threading.Thread(
target=self._save, args=(image, genre, filename))
save_thread.start()
# Uncomment these if stats service re-run in the future
# if upload:
# upload_thread = threading.Thread(
# target=self._upload, args=(image, genre, filename))

View File

@ -40,8 +40,7 @@ class StorageUI(UI):
Returns:
bool, if in MATERIAL_CHECK, appear and match_appear_on
"""
return self.appear(MATERIAL_CHECK, offset=(20, 20), interval=interval) \
and MATERIAL_CHECK.match_appear_on(self.device.image)
return self.match_template_color(MATERIAL_CHECK, offset=(20, 20), interval=interval)
def _storage_enter_material(self, skip_first_screenshot=True):
"""

View File

@ -243,18 +243,23 @@ class RewardTacticalClass(Dock):
book (Book):
skip_first_screenshot (bool):
"""
logger.info(f'Book select {book}')
interval = Timer(2, count=6)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if not book.check_selected(self.device.image):
self.device.click(book.button)
self.device.sleep((0.3, 0.5))
else:
# End
if book.check_selected(self.device.image):
break
if interval.reached():
self.device.click(book.button)
interval.reset()
continue
def _tactical_books_filter_exp(self):
"""
Complex filter to remove specific grade

View File

@ -477,7 +477,7 @@ class UI(InfoHandler):
return True
# Dorm popup
if self.appear(DORM_INFO, offset=(30, 30), threshold=0.75, interval=3):
if self.appear(DORM_INFO, offset=(30, 30), similarity=0.75, interval=3):
self.device.click(DORM_INFO)
return True
if self.appear_then_click(DORM_FEED_CANCEL, offset=(30, 30), interval=3):

View File

@ -24,6 +24,7 @@ MAIN_TAB_SWITCH_WHITE = Button(area={'cn': (966, 548, 999, 582), 'en': (966, 548
MISSION_NOTICE_WHITE = Button(area={'cn': (923, 657, 947, 671), 'en': (923, 657, 947, 671), 'jp': (923, 657, 947, 671), 'tw': (923, 657, 947, 671)}, color={'cn': (227, 168, 159), 'en': (227, 168, 159), 'jp': (227, 168, 159), 'tw': (227, 168, 159)}, button={'cn': (923, 657, 947, 671), 'en': (923, 657, 947, 671), 'jp': (923, 657, 947, 671), 'tw': (923, 657, 947, 671)}, file={'cn': './assets/cn/ui_white/MISSION_NOTICE_WHITE.png', 'en': './assets/en/ui_white/MISSION_NOTICE_WHITE.png', 'jp': './assets/jp/ui_white/MISSION_NOTICE_WHITE.png', 'tw': './assets/tw/ui_white/MISSION_NOTICE_WHITE.png'})
POPUP_CANCEL_WHITE = Button(area={'cn': (487, 491, 531, 513), 'en': (471, 492, 547, 513), 'jp': (481, 490, 534, 516), 'tw': (487, 491, 531, 513)}, color={'cn': (214, 214, 214), 'en': (205, 206, 205), 'jp': (202, 203, 202), 'tw': (214, 214, 214)}, button={'cn': (487, 491, 531, 513), 'en': (471, 492, 547, 513), 'jp': (481, 490, 534, 516), 'tw': (487, 491, 531, 513)}, file={'cn': './assets/cn/ui_white/POPUP_CANCEL_WHITE.png', 'en': './assets/en/ui_white/POPUP_CANCEL_WHITE.png', 'jp': './assets/jp/ui_white/POPUP_CANCEL_WHITE.png', 'tw': './assets/cn/ui_white/POPUP_CANCEL_WHITE.png'})
POPUP_CONFIRM_WHITE = Button(area={'cn': (746, 494, 791, 515), 'en': (727, 495, 810, 515), 'jp': (743, 491, 796, 518), 'tw': (746, 494, 791, 515)}, color={'cn': (133, 216, 255), 'en': (107, 207, 255), 'jp': (109, 207, 255), 'tw': (133, 216, 255)}, button={'cn': (746, 494, 791, 515), 'en': (727, 495, 810, 515), 'jp': (743, 491, 796, 518), 'tw': (746, 494, 791, 515)}, file={'cn': './assets/cn/ui_white/POPUP_CONFIRM_WHITE.png', 'en': './assets/en/ui_white/POPUP_CONFIRM_WHITE.png', 'jp': './assets/jp/ui_white/POPUP_CONFIRM_WHITE.png', 'tw': './assets/cn/ui_white/POPUP_CONFIRM_WHITE.png'})
POPUP_CONFIRM_WHITE_BATTLEPASS = Button(area={'cn': (744, 490, 795, 513), 'en': (744, 490, 795, 513), 'jp': (744, 490, 795, 513), 'tw': (744, 490, 795, 513)}, color={'cn': (119, 211, 255), 'en': (119, 211, 255), 'jp': (119, 211, 255), 'tw': (119, 211, 255)}, button={'cn': (744, 490, 795, 513), 'en': (744, 490, 795, 513), 'jp': (744, 490, 795, 513), 'tw': (744, 490, 795, 513)}, file={'cn': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png', 'en': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png', 'jp': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png', 'tw': './assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png'})
POPUP_SINGLE_WHITE = Button(area={'cn': (623, 493, 668, 515), 'en': (623, 493, 668, 515), 'jp': (623, 493, 668, 515), 'tw': (623, 493, 668, 515)}, color={'cn': (131, 215, 255), 'en': (131, 215, 255), 'jp': (131, 215, 255), 'tw': (131, 215, 255)}, button={'cn': (623, 493, 668, 515), 'en': (623, 493, 668, 515), 'jp': (623, 493, 668, 515), 'tw': (623, 493, 668, 515)}, file={'cn': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png', 'en': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png', 'jp': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png', 'tw': './assets/cn/ui_white/POPUP_SINGLE_WHITE.png'})
REWARD_1_WHITE = Button(area={'cn': (437, 278, 496, 306), 'en': (411, 283, 523, 300), 'jp': (442, 279, 491, 305), 'tw': (441, 280, 490, 306)}, color={'cn': (255, 193, 97), 'en': (255, 212, 150), 'jp': (255, 195, 101), 'tw': (255, 199, 111)}, button={'cn': (437, 278, 496, 306), 'en': (411, 283, 523, 300), 'jp': (442, 279, 491, 305), 'tw': (441, 280, 490, 306)}, file={'cn': './assets/cn/ui_white/REWARD_1_WHITE.png', 'en': './assets/en/ui_white/REWARD_1_WHITE.png', 'jp': './assets/jp/ui_white/REWARD_1_WHITE.png', 'tw': './assets/tw/ui_white/REWARD_1_WHITE.png'})
REWARD_2_WHITE = Button(area={'cn': (436, 419, 497, 448), 'en': (411, 425, 523, 442), 'jp': (442, 421, 492, 446), 'tw': (439, 422, 490, 448)}, color={'cn': (255, 192, 94), 'en': (255, 212, 146), 'jp': (255, 196, 103), 'tw': (255, 198, 109)}, button={'cn': (436, 419, 497, 448), 'en': (411, 425, 523, 442), 'jp': (442, 421, 492, 446), 'tw': (439, 422, 490, 448)}, file={'cn': './assets/cn/ui_white/REWARD_2_WHITE.png', 'en': './assets/en/ui_white/REWARD_2_WHITE.png', 'jp': './assets/jp/ui_white/REWARD_2_WHITE.png', 'tw': './assets/tw/ui_white/REWARD_2_WHITE.png'})