2024-08-10 18:27:40 +00:00
|
|
|
import re
|
|
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
|
from module.base.timer import Timer
|
|
|
|
from module.base.utils import crop
|
|
|
|
from module.logger import logger
|
|
|
|
from module.ocr.ocr import Digit, DigitCounter
|
|
|
|
from tasks.base.ui import UI
|
|
|
|
from tasks.combat.assets.assets_combat_stamina_status import *
|
|
|
|
|
|
|
|
|
|
|
|
class StaminaOcr(DigitCounter):
|
|
|
|
def after_process(self, result):
|
|
|
|
result = super().after_process(result)
|
|
|
|
# The trailblaze power icon is recognized as 买
|
|
|
|
# OCR_TRAILBLAZE_POWER includes the icon because the length varies by value
|
|
|
|
result = re.sub(r'[买米装来:()]', '', result)
|
|
|
|
# 61240 -> 6/240
|
|
|
|
result = re.sub(r'1240$', '/240', result)
|
|
|
|
# 0*0/24 -> 0/240
|
|
|
|
result = re.sub(r'24$', '240', result)
|
2024-08-11 09:18:16 +00:00
|
|
|
# * 50/2401+)
|
|
|
|
result = result.replace('/2401', '/240')
|
2024-08-10 18:27:40 +00:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
class ReservedOcr(Digit):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ImmersifierOcr(DigitCounter):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class DataStaminaStatus(BaseModel):
|
|
|
|
stamina: int | None
|
|
|
|
reserved: int | None
|
|
|
|
immersifier: int | None
|
|
|
|
|
|
|
|
|
|
|
|
class StaminaStatus(UI):
|
|
|
|
@staticmethod
|
|
|
|
def get_stamina_status(image) -> DataStaminaStatus:
|
|
|
|
"""
|
|
|
|
Update trailblaze power, stored trailblaze power, immersifier
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
int: Stamina, or None if stamina not displayed or error on OCR
|
|
|
|
int: Reserved stamina
|
|
|
|
int: Immersifier
|
|
|
|
"""
|
|
|
|
for button in [STAMINA_ICON, RESERVED_ICON, IMMERSIFIER_ICON]:
|
|
|
|
button.load_search(ICON_SEARCH.area)
|
|
|
|
|
|
|
|
stamina = None
|
|
|
|
if STAMINA_ICON.match_template(image):
|
|
|
|
STAMINA_OCR.load_offset(STAMINA_ICON)
|
|
|
|
im = crop(image, STAMINA_OCR.button, copy=False)
|
|
|
|
stamina, _, total = StaminaOcr(STAMINA_OCR).ocr_single_line(im, direct_ocr=True)
|
|
|
|
if total > 240 or total == 0:
|
|
|
|
logger.warning(f'Unexpected stamina total: {total}')
|
|
|
|
stamina = None
|
|
|
|
|
|
|
|
reserved = None
|
|
|
|
if RESERVED_ICON.match_template(image):
|
|
|
|
RESERVED_OCR.load_offset(RESERVED_ICON)
|
|
|
|
im = crop(image, RESERVED_OCR.button, copy=False)
|
|
|
|
reserved = ReservedOcr(RESERVED_OCR).ocr_single_line(im, direct_ocr=True)
|
|
|
|
if reserved > 2400:
|
|
|
|
logger.warning(f'Unexpected reserved value: {reserved}')
|
|
|
|
reserved = None
|
|
|
|
|
|
|
|
immersifier = None
|
|
|
|
if IMMERSIFIER_ICON.match_template(image):
|
|
|
|
IMMERSIFIER_OCR.load_offset(IMMERSIFIER_ICON)
|
|
|
|
im = crop(image, IMMERSIFIER_OCR.button, copy=False)
|
|
|
|
immersifier, _, total = StaminaOcr(IMMERSIFIER_OCR).ocr_single_line(im, direct_ocr=True)
|
|
|
|
if total != 8:
|
|
|
|
logger.warning(f'Unexpected immersifier total: {total}')
|
|
|
|
immersifier = None
|
|
|
|
|
|
|
|
return DataStaminaStatus(
|
|
|
|
stamina=stamina,
|
|
|
|
reserved=reserved,
|
|
|
|
immersifier=immersifier,
|
|
|
|
)
|
|
|
|
|
|
|
|
def update_stamina_status(
|
|
|
|
self,
|
|
|
|
image=None,
|
|
|
|
skip_first_screenshot=True,
|
|
|
|
expect_stamina=False,
|
|
|
|
expect_reserved=False,
|
|
|
|
expect_immersifier=False,
|
|
|
|
) -> DataStaminaStatus:
|
|
|
|
"""
|
|
|
|
Update stamina status with retry
|
|
|
|
|
|
|
|
Args:
|
|
|
|
image: Detect given image only, no new screenshot will be taken
|
|
|
|
and all expect_* are considered False
|
|
|
|
skip_first_screenshot:
|
|
|
|
expect_stamina:
|
|
|
|
True to expect stamina exists, retry detect if it wasn't
|
|
|
|
expect_reserved:
|
|
|
|
expect_immersifier:
|
|
|
|
|
|
|
|
Pages:
|
|
|
|
in: page_guild, Survival_Index, Simulated_Universe
|
|
|
|
or page_rogue, LEVEL_CONFIRM
|
|
|
|
or rogue, REWARD_CLOSE
|
|
|
|
"""
|
|
|
|
timeout = Timer(1, count=2).start()
|
|
|
|
if image is None:
|
|
|
|
image = self.device.image
|
|
|
|
use_cached_image = False
|
|
|
|
else:
|
|
|
|
skip_first_screenshot = True
|
|
|
|
use_cached_image = True
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
if skip_first_screenshot:
|
|
|
|
skip_first_screenshot = False
|
|
|
|
else:
|
|
|
|
self.device.screenshot()
|
|
|
|
image = self.device.image
|
|
|
|
|
|
|
|
# Timeout
|
|
|
|
if timeout.reached():
|
|
|
|
logger.warning('dungeon_update_stamina() timeout')
|
|
|
|
return DataStaminaStatus(
|
|
|
|
stamina=None,
|
|
|
|
reserved=None,
|
|
|
|
immersifier=None,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Ocr
|
|
|
|
status = self.get_stamina_status(image)
|
|
|
|
valid = True
|
|
|
|
if expect_stamina and status.stamina is None:
|
|
|
|
valid = False
|
|
|
|
if expect_reserved and status.reserved is None:
|
|
|
|
valid = False
|
|
|
|
if expect_immersifier and status.immersifier is None:
|
|
|
|
valid = False
|
|
|
|
if status.stamina is None and status.reserved is None and status.immersifier is None:
|
|
|
|
logger.warning('update_stamina_status: No icon detected')
|
|
|
|
valid = False
|
|
|
|
|
|
|
|
# Write config
|
|
|
|
with self.config.multi_set():
|
|
|
|
if status.stamina is not None:
|
|
|
|
self.config.stored.TrailblazePower.value = status.stamina
|
|
|
|
if status.reserved is not None:
|
|
|
|
self.config.stored.Reserved.value = status.reserved
|
|
|
|
if status.immersifier is not None:
|
2024-08-14 07:07:01 +00:00
|
|
|
self.config.stored.Immersifier.value = status.immersifier
|
2024-08-10 18:27:40 +00:00
|
|
|
|
|
|
|
if use_cached_image or valid:
|
|
|
|
return status
|
|
|
|
else:
|
|
|
|
continue
|