diff --git a/assets/share/item/synthesize/ENTRY_ITEM_FROM.png b/assets/share/item/synthesize/ENTRY_ITEM_FROM.png new file mode 100644 index 000000000..c233deab1 Binary files /dev/null and b/assets/share/item/synthesize/ENTRY_ITEM_FROM.png differ diff --git a/assets/share/item/synthesize/ENTRY_ITEM_TO.png b/assets/share/item/synthesize/ENTRY_ITEM_TO.png new file mode 100644 index 000000000..d62184a5a Binary files /dev/null and b/assets/share/item/synthesize/ENTRY_ITEM_TO.png differ diff --git a/assets/share/item/synthesize/ITEM_NAME.png b/assets/share/item/synthesize/ITEM_NAME.png new file mode 100644 index 000000000..01f92f4fc Binary files /dev/null and b/assets/share/item/synthesize/ITEM_NAME.png differ diff --git a/assets/share/item/synthesize/SWITCH_RARITY.png b/assets/share/item/synthesize/SWITCH_RARITY.png new file mode 100644 index 000000000..5d87b4780 Binary files /dev/null and b/assets/share/item/synthesize/SWITCH_RARITY.png differ diff --git a/assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png b/assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png new file mode 100644 index 000000000..264f7004f Binary files /dev/null and b/assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png differ diff --git a/assets/share/item/synthesize/SYNTHESIZE_MINUS.png b/assets/share/item/synthesize/SYNTHESIZE_MINUS.png new file mode 100644 index 000000000..359d68b1e Binary files /dev/null and b/assets/share/item/synthesize/SYNTHESIZE_MINUS.png differ diff --git a/assets/share/item/synthesize/SYNTHESIZE_PLUS.png b/assets/share/item/synthesize/SYNTHESIZE_PLUS.png new file mode 100644 index 000000000..513b8ad80 Binary files /dev/null and b/assets/share/item/synthesize/SYNTHESIZE_PLUS.png differ diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index e144c31e3..50f941b87 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -30,10 +30,11 @@ class CombatObtain(PlannerMixin): # True to exit and reenter to get obtained items obtain_frequent_check = False - def _obtain_enter(self, entry, skip_first_screenshot=True): + def _obtain_enter(self, entry, appear_button, skip_first_screenshot=True): """ Args: entry: Item entry + appear_button: skip_first_screenshot: Pages: @@ -41,7 +42,7 @@ class CombatObtain(PlannerMixin): out: ITEM_CLOSE """ logger.info(f'Obtain enter {entry}') - self.interval_clear(COMBAT_PREPARE) + self.interval_clear(appear_button) while 1: if skip_first_screenshot: skip_first_screenshot = False @@ -50,9 +51,9 @@ class CombatObtain(PlannerMixin): if self.appear(ITEM_CLOSE): break - if self.appear(COMBAT_PREPARE, interval=2): + if self.appear(appear_button, interval=2): self.device.click(entry) - self.interval_reset(COMBAT_PREPARE) + self.interval_reset(appear_button) continue # Wait animation @@ -70,9 +71,10 @@ class CombatObtain(PlannerMixin): logger.warning('Wait obtain item timeout') break - def _obtain_close(self, skip_first_screenshot=True): + def _obtain_close(self, check_button, skip_first_screenshot=True): """ Args: + check_button: skip_first_screenshot: Pages: @@ -87,8 +89,13 @@ class CombatObtain(PlannerMixin): else: self.device.screenshot() - if not self.appear(ITEM_CLOSE) and self.appear(COMBAT_PREPARE) and self.appear(MAY_OBTAIN): - break + if not self.appear(ITEM_CLOSE): + if callable(check_button): + if check_button(): + break + else: + if self.appear(check_button): + break if self.appear_then_click(ITEM_CLOSE, interval=2): continue @@ -219,7 +226,7 @@ class CombatObtain(PlannerMixin): logger.error(f'No obtain entry for {entry_index}') break - self._obtain_enter(entry) + self._obtain_enter(entry, appear_button=COMBAT_PREPARE) item = self._obtain_parse() if item.item == KEYWORDS_ITEM_CURRENCY.Trailblaze_EXP: logger.warning('Trailblaze_EXP is in obtain list, OBTAIN_TRAILBLAZE_EXP may need to verify') @@ -229,7 +236,7 @@ class CombatObtain(PlannerMixin): items.append(item) index += 1 prev = item - self._obtain_close() + self._obtain_close(check_button=MAY_OBTAIN) logger.hr('Obtained Result') for item in items: diff --git a/tasks/item/assets/assets_item_synthesize.py b/tasks/item/assets/assets_item_synthesize.py new file mode 100644 index 000000000..863e4ec0a --- /dev/null +++ b/tasks/item/assets/assets_item_synthesize.py @@ -0,0 +1,75 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +ENTRY_ITEM_FROM = ButtonWrapper( + name='ENTRY_ITEM_FROM', + share=Button( + file='./assets/share/item/synthesize/ENTRY_ITEM_FROM.png', + area=(825, 408, 905, 488), + search=(805, 388, 925, 508), + color=(70, 105, 114), + button=(825, 408, 905, 488), + ), +) +ENTRY_ITEM_TO = ButtonWrapper( + name='ENTRY_ITEM_TO', + share=Button( + file='./assets/share/item/synthesize/ENTRY_ITEM_TO.png', + area=(815, 193, 915, 293), + search=(795, 173, 935, 313), + color=(157, 143, 120), + button=(815, 193, 915, 293), + ), +) +ITEM_NAME = ButtonWrapper( + name='ITEM_NAME', + share=Button( + file='./assets/share/item/synthesize/ITEM_NAME.png', + area=(657, 95, 1057, 118), + search=(637, 75, 1077, 138), + color=(92, 100, 111), + button=(657, 95, 1057, 118), + ), +) +SWITCH_RARITY = ButtonWrapper( + name='SWITCH_RARITY', + share=Button( + file='./assets/share/item/synthesize/SWITCH_RARITY.png', + area=(1180, 262, 1216, 298), + search=(1160, 242, 1236, 318), + color=(89, 96, 124), + button=(1180, 262, 1216, 298), + ), +) +SYNTHESIZE_AMOUNT = ButtonWrapper( + name='SYNTHESIZE_AMOUNT', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png', + area=(683, 548, 1034, 568), + search=(663, 528, 1054, 588), + color=(122, 132, 147), + button=(683, 548, 1034, 568), + ), +) +SYNTHESIZE_MINUS = ButtonWrapper( + name='SYNTHESIZE_MINUS', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_MINUS.png', + area=(575, 569, 605, 589), + search=(555, 549, 625, 609), + color=(235, 235, 235), + button=(575, 569, 605, 589), + ), +) +SYNTHESIZE_PLUS = ButtonWrapper( + name='SYNTHESIZE_PLUS', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_PLUS.png', + area=(1125, 567, 1155, 589), + search=(1105, 547, 1175, 609), + color=(228, 228, 228), + button=(1125, 567, 1155, 589), + ), +) diff --git a/tasks/item/synthesize.py b/tasks/item/synthesize.py new file mode 100644 index 000000000..7867c0e04 --- /dev/null +++ b/tasks/item/synthesize.py @@ -0,0 +1,211 @@ +import cv2 +import numpy as np + +from module.base.timer import Timer +from module.base.utils import color_similarity_2d, crop, image_size +from module.logger import logger +from module.ocr.ocr import Ocr +from tasks.base.page import page_synthesize +from tasks.combat.obtain import CombatObtain +from tasks.item.assets.assets_item_synthesize import * +from tasks.planner.keywords import ITEM_CLASSES +from tasks.planner.model import ObtainedAmmount +from tasks.planner.scan import OcrItemName + +RARITY_COLOR = { + 'green': (68, 127, 124), + 'blue': (76, 124, 191), + 'purple': (141, 97, 203), +} + + +def image_color_count(image, color, threshold=221): + mask = color_similarity_2d(image, color=color) + cv2.inRange(mask, threshold, 255, dst=mask) + sum_ = cv2.countNonZero(mask) + return sum_ + + +class WhiteStrip(Ocr): + def pre_process(self, image): + mask = color_similarity_2d(image, color=(255, 255, 255)) + mask = cv2.inRange(mask, 180, 255, dst=mask) + + mask = np.mean(mask, axis=0) + point = np.array(cv2.findNonZero(mask))[:, 0, 1] + x1, x2 = point.min(), point.max() + + _, y = image_size(image) + image = crop(image, (x1 - 5, 0, x2 + 5, y), copy=False) + return image + + +class SynthesizeItemName(OcrItemName, WhiteStrip): + pass + + +class Synthesize(CombatObtain): + def item_get_rarity(self, button) -> str | None: + """ + Args: + button: + + Returns: + str: Rarity color or None if no match + + Pages: + in: page_synthesize + """ + image = self.image_crop(button) + image = cv2.GaussianBlur(image, (3, 3), 0) + x2, y2 = image_size(image) + y1 = y2 - int(y2 // 4) + image = crop(image, (0, y1, x2, y2)) + # self.device.image_show(image) + # print(image.shape) + + # Must contain 30% target color at icon bottom + minimum = x2 * (y2 - y1) * 0.3 + for rarity, color in RARITY_COLOR.items(): + count = image_color_count(image, color=color, threshold=221) + # print(rarity, count, minimum) + if count > minimum: + return rarity + + return None + + def item_get_rarity_retry(self, button, skip_first_screenshot=True) -> str | None: + timeout = Timer(1, count=3).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + current = self.item_get_rarity(button) + logger.attr('SynthesizeRarity', current) + if current is not None: + return current + if timeout.reached(): + logger.warning(f'item_get_rarity_retry timeout') + return None + + def synthesize_rarity_set(self, rarity: str, skip_first_screenshot=True) -> bool: + """ + Args: + rarity: "green" or "blue" + note that rarity is the one you consume to synthesize + skip_first_screenshot: + + Returns: + bool: If switched + + Pages: + in: page_synthesize + """ + logger.info(f'item_synthesize_rarity_set: {rarity}') + + switched = False + self.interval_clear(page_synthesize.check_button) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + current = self.item_get_rarity(ENTRY_ITEM_FROM) + logger.attr('SynthesizeRarity', current) + if current is not None and current == rarity: + break + + # Click + if self.ui_page_appear(page_synthesize, interval=2): + self.device.click(SWITCH_RARITY) + switched = True + continue + + return switched + + def synthesize_rarity_reset(self, skip_first_screenshot=True): + """ + Reset rarity switch, so current item will pin on the first row + + Returns: + bool: If success + """ + + current = self.item_get_rarity_retry(ENTRY_ITEM_FROM) + if current == 'blue': + r1, r2 = 'green', 'blue' + elif current == 'green': + r1, r2 = 'blue', 'green' + else: + logger.error(f'item_synthesize_rarity_reset: Unknown current rarity {current}') + return False + + self.synthesize_rarity_set(r1, skip_first_screenshot=skip_first_screenshot) + self.synthesize_rarity_set(r2, skip_first_screenshot=True) + return True + + def synthesize_obtain_get(self) -> list[ObtainedAmmount]: + """ + Update item amount from synthesize page + """ + logger.hr('Synthesize obtain get', level=2) + items = [] + + def obtain_end(): + return self.item_get_rarity(ENTRY_ITEM_FROM) is not None + + # Purple + self.synthesize_rarity_set('blue') + self._obtain_enter(ENTRY_ITEM_TO, appear_button=page_synthesize.check_button) + item = self._obtain_parse() + if item is not None: + items.append(item) + self._obtain_close(check_button=obtain_end) + # Blue + self._obtain_enter(ENTRY_ITEM_FROM, appear_button=page_synthesize.check_button) + item = self._obtain_parse() + if item is not None: + items.append(item) + self._obtain_close(check_button=obtain_end) + # Green + self.synthesize_rarity_set('green') + self._obtain_enter(ENTRY_ITEM_FROM, appear_button=page_synthesize.check_button) + item = self._obtain_parse() + if item is not None: + items.append(item) + self._obtain_close(check_button=obtain_end) + + logger.hr('Obtained Result') + for item in items: + # Pretend everything is full + # item.value += 1000 + logger.info(f'Obtained item: {item.item.name}, {item.value}') + """ + <<< OBTAIN GET RESULT >>> + ItemAmount: Arrow_of_the_Starchaser, 15 + ItemAmount: Arrow_of_the_Demon_Slayer, 68 + ItemAmount: Arrow_of_the_Beast_Hunter, 85 + """ + self.planner.load_obtained_amount(items) + self.planner_write() + return items + + def synthesize_get_item(self): + ocr = SynthesizeItemName(ITEM_NAME) + item = ocr.matched_single_line(self.device.image, keyword_classes=ITEM_CLASSES) + if item is None: + logger.warning('synthesize_get_item: Unknown item name') + return None + + return item + + +if __name__ == '__main__': + self = Synthesize('src') + self.device.screenshot() + self.synthesize_obtain_get() diff --git a/tasks/planner/scan.py b/tasks/planner/scan.py index c8ae60745..5580c177b 100644 --- a/tasks/planner/scan.py +++ b/tasks/planner/scan.py @@ -21,8 +21,11 @@ class OcrItemName(Ocr): def after_process(self, result): result = result.replace('念火之心', '忿火之心') result = re.sub('工造机$', '工造机杼', result) - result = re.sub('工造轮', '工造迴轮', result) + result = re.sub('工造迥?轮', '工造迴轮', result) result = re.sub('月狂[療撩]?牙', '月狂獠牙', result) + # Error words on blank background + result = re.sub('^[國東]', '', result) + result = re.sub('時$', '', result) return result