StarRailCopilot/module/ui/draggable_list.py

198 lines
5.9 KiB
Python
Raw Normal View History

2023-05-30 18:20:45 +00:00
from typing import Optional
import numpy as np
from module.base.base import ModuleBase
from module.base.button import ButtonWrapper
from module.base.timer import Timer
from module.base.utils import area_size
from module.logger import logger
from module.ocr.keyword import Keyword
from module.ocr.ocr import OcrResultButton
class DraggableList:
"""
A wrapper to handle draggable lists like
- Simulated Universe
- Calyx (Golden)
- Calyx (Crimson)
- Stagnant Shadow
- Cavern of Corrosion
"""
def __init__(
self,
name,
keyword_class,
ocr_class,
search_button: ButtonWrapper,
):
"""
Args:
name:
keyword_class: Keyword
search_button:
"""
self.name = name
self.keyword_class = keyword_class
self.ocr_class = ocr_class
self.known_rows = list(keyword_class.instances.values())
self.search_button = search_button
self.row_min = 1
self.row_max = len(self.known_rows)
self.cur_min = 1
self.cur_max = 1
self.cur_buttons: list[OcrResultButton] = []
self.drag_vector = (0.65, 0.85)
def __str__(self):
return f'DraggableList({self.name})'
__repr__ = __str__
def __eq__(self, other):
return str(self) == str(other)
def __hash__(self):
return hash(self.name)
def keyword2index(self, row: Keyword) -> int:
try:
return self.known_rows.index(row) + 1
except ValueError:
logger.warning(f'Row "{row}" does not belong to {self}')
return 0
def keyword2button(self, row: Keyword) -> Optional[OcrResultButton]:
for button in self.cur_buttons:
if button == row:
return button
logger.warning(f'Keyword {row} is not in current rows of {self}')
logger.warning(f'Current rows: {self.cur_buttons}')
return None
def load_rows(self, main: ModuleBase):
"""
Parse current rows to get list position.
"""
self.cur_buttons = self.ocr_class(self.search_button) \
.matched_ocr(main.device.image, keyword_class=self.keyword_class)
# Get indexes
indexes = [self.keyword2index(row.matched_keyword) for row in self.cur_buttons]
indexes = [index for index in indexes if index]
# Check row order
if len(indexes) >= 2:
if not np.all(np.diff(indexes) > 0):
logger.warning(f'Rows given to {self} are not ascending sorted')
if not indexes:
logger.warning(f'No valid rows loaded into {self}')
return
self.cur_min = min(indexes)
self.cur_max = max(indexes)
logger.attr(self.name, f'{self.cur_min} - {self.cur_max}')
def _page_drag(self, direction: str, main: ModuleBase):
vector = np.random.uniform(*self.drag_vector)
width, height = area_size(self.search_button.button)
if direction == 'down':
vector = (0, vector * height)
elif direction == 'up':
vector = (0, -vector * height)
elif direction == 'left':
vector = (-vector * width, 0)
elif direction == 'right':
vector = (vector * width, 0)
else:
logger.warning(f'Unknown drag direction: {direction}')
return
main.device.swipe_vector(
vector, box=self.search_button.button, random_range=(-10, -10, 10, 10), name=f'{self.name}_DRAG')
def insight_row(self, row: Keyword, main: ModuleBase, skip_first_screenshot=True) -> bool:
"""
Args:
row:
main:
skip_first_screenshot:
Returns:
If success
"""
row_index = self.keyword2index(row)
if not row_index:
logger.warning(f'Insight row {row} but index unknown')
return False
logger.info(f'Insight row: {row}, index={row_index}')
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_rows(main=main)
# End
if self.cur_min <= row_index <= self.cur_max:
break
# Drag pages
if row_index < self.cur_min:
self._page_drag('down', main=main)
elif self.cur_max < row_index:
self._page_drag('up', main=main)
# Wait for bottoming out
main.wait_until_stable(self.search_button, timer=Timer(0, count=0), timeout=Timer(1.5, count=5))
skip_first_screenshot = True
return True
def is_row_selected(self, row: Keyword, main: ModuleBase) -> bool:
button = self.keyword2button(row)
if not button:
return False
# Having gold letters
if main.image_color_count(button, color=(190, 175, 124), threshold=221, count=50):
return True
return False
def select_row(self, row: Keyword, main: ModuleBase, skip_first_screenshot=True):
"""
Args:
row:
main:
skip_first_screenshot:
Returns:
If success
"""
result = self.insight_row(row, main=main, skip_first_screenshot=skip_first_screenshot)
if not result:
return False
logger.info(f'Select row: {row}')
skip_first_screenshot = True
interval = Timer(5)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
# End
if self.is_row_selected(row, main=main):
logger.info('Row selected')
break
# Click
if interval.reached():
main.device.click(self.keyword2button(row))
interval.reset()