StarRailCopilot/tasks/rogue/blessing.py
2023-08-11 02:43:06 +08:00

298 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
import numpy as np
from module.base.filter import MultiLangFilter
from module.base.timer import Timer
from module.base.utils import area_offset, get_color
from module.logger import logger
from module.ocr.keyword import Keyword
from module.ocr.ocr import Ocr, OcrResultButton, DigitCounter
from tasks.rogue.assets.assets_rogue_blessing import *
from tasks.rogue.keywords import *
from tasks.rogue.preset import *
from tasks.rogue.ui import RogueUI
REGEX_PUNCTUATION = re.compile(r'[ ,.\'"“”,。:!??·•—/()()「」『』【】《》]')
def parse_name(n):
n = REGEX_PUNCTUATION.sub('', str(n)).lower()
return n
def get_regex_from_keyword_name(keyword, attr_name):
string = ""
for instance in keyword.instances.values():
if hasattr(instance, attr_name):
for name in instance.__getattribute__(attr_name):
string += f"{name}|"
# some pattern contain each other, make sure each pattern end with "-" or the end of string
return f"(?:({string[:-1]})(?:-|$))?"
# normal blessing
pattern = ""
BLESSING_FILTER_ATTR = tuple()
PATH_ATTR_NAME = 'path_name'
path_regex = get_regex_from_keyword_name(RoguePath, PATH_ATTR_NAME)
pattern += path_regex
BLESSING_FILTER_ATTR += (PATH_ATTR_NAME,)
pattern += "([123])?-?"
BLESSING_FILTER_ATTR += ("rarity",)
BLESSING_ATTR_NAME = 'blessing_name'
blessing_regex = get_regex_from_keyword_name(RogueBlessing, BLESSING_ATTR_NAME)
pattern += blessing_regex
BLESSING_FILTER_ATTR += (BLESSING_ATTR_NAME,)
FILETER_REGEX = re.compile(pattern)
BLESSING_FILTER_PRESET = ("reset", "same_path", "random")
print(FILETER_REGEX)
print(BLESSING_FILTER_ATTR)
print(BLESSING_FILTER_PRESET)
BLESSING_FILTER = MultiLangFilter(FILETER_REGEX, BLESSING_FILTER_ATTR, BLESSING_FILTER_PRESET)
# resonance
RESONANCE_ATTR_NAME = 'resonance_name'
pattern = get_regex_from_keyword_name(RogueResonance, 'resonance')
FILETER_REGEX = re.compile(pattern)
RESONANCE_FILTER_PRESET = ("random",)
RESONANCE_FILTER = MultiLangFilter(FILETER_REGEX, (RESONANCE_ATTR_NAME,), RESONANCE_FILTER_PRESET)
class RogueBuffOcr(Ocr):
merge_thres_x = 40
def after_process(self, result):
result = super().after_process(result)
if self.lang == 'ch':
replace_pattern_dict = {
"蓬失": "蓬矢",
"柘弓危失": "柘弓危矢",
"飞虹凿齿": "飞虹诛凿齿",
"天培步危": "天棓步危",
"云[摘销]": "云镝",
"制桑": "制穹桑",
"乌号基": "乌号綦",
"追摩物": "追孽物",
"特月": "狩月",
"彤弓素增": "彤弓素矰",
"苦表": "苦衷",
"[沦沧]肌髓": "沦浃肌髓",
"进发": "迸发",
"永缩体": "永坍缩体",
"完美体验:绒默": "完美体验:缄默",
"^灾$": "禳灾",
}
for pattern, replace in replace_pattern_dict.items():
result = re.sub(pattern, replace, result)
return result
class RogueBlessingUI(RogueUI):
def get_blessing_count(self) -> int:
"""
Returns: The number of blessing
"""
color = get_color(self.device.image, BOTTOM_WHITE_BAR.area)
mean = np.mean(color)
return int(mean // 60) # the magic number that maps blessing num with mean_color
def buffs_recognition(self):
ocr = RogueBuffOcr(OCR_ROGUE_BUFF)
results = ocr.matched_ocr(self.device.image, [RogueBlessing, RogueResonance])
blessing_count = self.get_blessing_count()
if blessing_count != len(results):
logger.warning(f"The OCR result does not match the blessing count. "
f"Expect {blessing_count}, but recognized {len(results)} only.")
self.blessings = results
return results
def ui_select_blessing(self, blessing: OcrResultButton | None, skip_first_screenshot=True, enforce=False):
"""
Select buff once. Multiple calls needed if there's more than one time to choose
It might occur that all listed blessings are not recognized
So this method provides a hard code way to choose one, which fit in case when blessing num is 1-3
"""
def is_blessing_selected():
"""
There is a white border if a blessing is selected.
"""
top_border = area_offset(blessing.area, (0, -180))
return self.image_color_count(top_border, (255, 255, 255))
def is_select_blessing_complete():
"""
Case 1: back to main page
Case 2: choose curio
Case 3: another choose blessings, but no blessing is selected when the new selection page loaded
"""
return (self.is_in_main() or self.is_page_choose_curio()
or (self.is_page_choose_blessing() and not is_blessing_selected()))
interval = Timer(1)
if not blessing:
enforce = True
# start -> selected
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if is_blessing_selected():
if enforce:
logger.info("Buff selected (enforce)")
else:
logger.info(f"Buff {blessing} selected")
break
if interval.reached():
if enforce:
self.device.click(BLESSING_ENFORCE)
else:
self.device.click(blessing)
interval.reset()
skip_first_screenshot = True
# selected -> confirm
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if is_select_blessing_complete():
break
if interval.reached():
self.device.click(CONFIRM)
interval.reset()
def get_reset_count(self):
current, _, _ = DigitCounter(OCR_RESET_COUNT).ocr_single_line(self.device.image)
return current
def wait_until_blessing_loaded(self, skip_first_screenshot=True):
timeout = Timer(2, count=4).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
logger.warning('Wait blessing page loaded timeout')
return False
if self.image_color_count(BLESSING_STABLE_FLAG, (255, 255, 255)):
logger.info("Blessing page loaded")
return True
def reset_blessing_list(self, skip_first_screenshot=True):
if not self.is_page_choose_blessing():
return False
reset_count = self.get_reset_count()
if not reset_count:
return False
interval = Timer(1)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
new_count = self.get_reset_count()
if reset_count - new_count == 1:
logger.info("Reset once")
break
if interval.reached():
self.device.click(BLESSING_RESET)
interval.reset()
return True
class RogueBlessingSelector(RogueBlessingUI):
"""
Usage:
self = RogueBlessingSelector('alas')
self.device.screenshot()
self.buff_recognition()
self.select_blessing(self.)
"""
def apply_filter(self):
def match_ocr_result(matched_keyword: Keyword):
for blessing in self.blessings:
if blessing.matched_keyword == matched_keyword:
return blessing
return None
if not self.blessings:
return []
if isinstance(self.blessings[0].matched_keyword, RogueBlessing):
if self.config.Rogue_PresetBlessingFilter == 'preset-1':
BLESSING_FILTER.load(parse_name(BLESSING_PRESET_1))
if self.config.Rogue_PresetBlessingFilter == 'custom':
BLESSING_FILTER.load(parse_name(self.config.Rogue_CustomBlessingFilter))
if isinstance(self.blessings[0].matched_keyword, RogueResonance):
if len(self.blessings) == 1: # resonance can not be reset. So have not choice when there's only one option
return self.blessings
if self.config.Rogue_PresetResonanceFilter == 'preset-1':
RESONANCE_FILTER.load(parse_name(RESONANCE_PRESET_1))
if self.config.Rogue_PresetResonanceFilter == 'custom':
RESONANCE_FILTER.load(parse_name(self.config.Rogue_CustomResonanceFilter))
blessing_keywords = [blessing.matched_keyword for blessing in self.blessings]
priority = BLESSING_FILTER.apply(blessing_keywords)
priority = [option if isinstance(option, str) else match_ocr_result(option) for option in priority]
return priority
def select_blessing(self, priority: list):
if not self.blessings:
logger.info('No blessing recognized, randomly choose one')
self.ui_select_blessing(None, enforce=True)
return
if not len(priority):
logger.info('No blessing project satisfies current filter, randomly choose one')
choose = np.random.choice(self.blessings)
self.ui_select_blessing(choose)
return
for option in priority:
# preset
if isinstance(option, str):
if option.lower() == 'reset':
if self.reset_blessing_list():
self.wait_until_blessing_loaded()
self.buffs_recognition()
self.select_blessing(self.apply_filter())
return
else:
continue
if option.lower() == 'same_path':
chosen = False
for blessing in self.blessings:
if blessing.path_id == self.path.id:
self.ui_select_blessing(blessing)
chosen = True
if chosen:
return
else:
continue
if option.lower() == 'random':
choose = np.random.choice(self.blessings)
self.ui_select_blessing(choose)
return
if isinstance(option, OcrResultButton):
self.ui_select_blessing(option)
return