diff --git a/assets/share/item/synthesize/SYNTHESIZE_SLIDER.png b/assets/share/item/synthesize/SYNTHESIZE_SLIDER.png new file mode 100644 index 000000000..b6a4317c0 Binary files /dev/null and b/assets/share/item/synthesize/SYNTHESIZE_SLIDER.png differ diff --git a/module/ui/scroll.py b/module/ui/scroll.py index 6d9387612..ef303e103 100644 --- a/module/ui/scroll.py +++ b/module/ui/scroll.py @@ -248,6 +248,7 @@ class AdaptiveScroll(Scroll): 'prominence': 30, 'wlen': wlen, 'width': 2, + # 'distance': wlen / 2, } parameters.update(self.parameters) peaks, _ = signal.find_peaks(image, **parameters) diff --git a/tasks/item/assets/assets_item_synthesize.py b/tasks/item/assets/assets_item_synthesize.py index 4a9a89ea5..8ded80b55 100644 --- a/tasks/item/assets/assets_item_synthesize.py +++ b/tasks/item/assets/assets_item_synthesize.py @@ -83,3 +83,13 @@ SYNTHESIZE_PLUS = ButtonWrapper( button=(1125, 567, 1155, 589), ), ) +SYNTHESIZE_SLIDER = ButtonWrapper( + name='SYNTHESIZE_SLIDER', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_SLIDER.png', + area=(660, 575, 1070, 581), + search=(640, 555, 1090, 601), + color=(118, 96, 77), + button=(660, 575, 1070, 581), + ), +) diff --git a/tasks/item/slider.py b/tasks/item/slider.py new file mode 100644 index 000000000..6ec986ade --- /dev/null +++ b/tasks/item/slider.py @@ -0,0 +1,149 @@ +import numpy as np +from scipy import signal + +from module.base.button import ButtonWrapper, ClickButton +from module.base.timer import Timer +from module.base.utils import rgb2gray +from module.exception import ScriptError +from module.logger import logger +from tasks.base.ui import UI + + +class Slider: + def __init__( + self, + main: UI, + slider: ButtonWrapper, + parameters: dict = None, + background=5 + ): + """ + Args: + main: + slider: Slider button + parameters (dict): Parameters passing to scipy.find_peaks + background (int): + """ + self.main = main + self.slider = slider + + if parameters is None: + parameters = {} + self.parameters = parameters + self.background = background + + # Actual slider area + self.area = self.slider.area + + def cal_slider(self): + """ + Slider always have its left side fixed, right side movable depending on the maximum number + Calculate the right side, similar to AdaptiveScroll.match_color + """ + area = self.slider.area + area = (area[0], area[1] - self.background, area[2], area[3] + self.background) + image = self.main.image_crop(area, copy=False) + image = rgb2gray(image) + image = image.flatten('F') + wlen = area[3] - area[1] + total = area[2] - area[0] + + parameters = { + 'prominence': 15, + 'wlen': wlen, + 'width': 4, + 'distance': wlen / 2, + } + parameters.update(self.parameters) + peaks, _ = signal.find_peaks(image, **parameters) + peaks //= wlen + + # Ignore non-continuous peaks, which may be the letter to the right of slider + try: + right = np.where(np.diff(peaks) >= 3)[0][0] + peaks = peaks[:right + 1] + except IndexError: + pass + # Calculate actual slider area + try: + length = peaks[-1] + self.area = (area[0], area[1], area[0] + length + 1, area[3]) + except IndexError: + length = total + self.area = self.slider.area + logger.info(f'Slider length: {length}/{total}') + + def set(self, value: int, total: int, skip_first_screenshot=True): + """ + Args: + value: Value to set + total: Maximum amount of slider + skip_first_screenshot: + + Returns: + bool: If success + """ + logger.info(f'Slider set {value}/{total}') + if value > total: + raise ScriptError(f'Slider.set value {value} > total {total}') + if total <= 0: + raise ScriptError(f'Slider.set total {total} <= 0') + if value <= 0: + raise ScriptError(f'Slider.set value {value} <= 0') + if value == total == 1: + logger.info('Slider set total==1, no need to set') + return True + # if value == 1: + # logger.info('Slider set value==1, no need to set') + # return True + + self.cal_slider() + + # 18px is the width of controller + length = self.area[2] - self.area[0] - 18 + + left = int((value - 2) / (total - 1) * length) + self.area[0] + right = int((value - 1) / (total - 1) * length) + self.area[0] + if right <= left: + right = left + 1 + # 10px is the radius of slider controller + # When you somewhere on the slider, left edge of controller is where you click + # if right - left < 10: + # logger.info('Setting high maximum slider') + # left -= 10 + # right -= 10 + + # Limit in slider + left = max(min(self.area[2] - 1, left), self.area[0]) + right = max(min(self.area[2], right), self.area[0] + 1) + # Click the right half + left = int((left + right) / 2) + button = ClickButton( + (left, self.area[1], right, self.area[3]), + name=f'Slider_{value}_{total}') + # Pad click area to search + pad = 15 + detect = (right - pad, self.area[1], right + pad, self.area[3]) + logger.info(f'Controller button={button.button}, detect={detect}') + + interval = Timer(1, count=3) + trial = 0 + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.main.device.screenshot() + + # End + if trial > 3: + logger.warning('Slider.set failed after 3 trial') + return False + if self.main.image_color_count(detect, color=(255, 255, 255), threshold=221, count=50): + logger.info('Slider set done') + return True + + # Click + if interval.reached(): + self.main.device.click(button) + interval.reset() + trial += 1 diff --git a/tasks/item/synthesize.py b/tasks/item/synthesize.py index 168f44642..0dddf784f 100644 --- a/tasks/item/synthesize.py +++ b/tasks/item/synthesize.py @@ -1,16 +1,18 @@ import cv2 import numpy as np +import module.config.server as server from module.base.decorator import cached_property from module.base.timer import Timer from module.base.utils import SelectedGrids, color_similarity_2d, crop, image_size from module.exception import ScriptError from module.logger import logger -from module.ocr.ocr import Ocr +from module.ocr.ocr import Digit, Ocr from tasks.base.page import page_synthesize from tasks.combat.obtain import CombatObtain from tasks.item.assets.assets_item_synthesize import * from tasks.item.inventory import InventoryManager +from tasks.item.slider import Slider from tasks.planner.keywords import ITEM_CLASSES, ItemCalyx, ItemTrace from tasks.planner.keywords.classes import ItemBase from tasks.planner.model import ObtainedAmmount @@ -332,6 +334,18 @@ class Synthesize(CombatObtain): logger.error(f'Unexpected switch_row={switch_row} during loop') return False + def synthesize_amount_set(self, value: int, total: int): + """ + Args: + value: Value to set + total: Maximum amount of slider + """ + logger.hr('Synthesize amount set', level=2) + slider = Slider(main=self, slider=SYNTHESIZE_SLIDER) + slider.set(value, total) + ocr = Digit(SYNTHESIZE_AMOUNT, lang=server.lang) + self.ui_ensure_index( + value, letter=ocr, next_button=SYNTHESIZE_PLUS, prev_button=SYNTHESIZE_MINUS, interval=(0.1, 0.2)) if __name__ == '__main__':