Add: forgotten hall stage goto

This commit is contained in:
Hengyu 2023-06-30 00:32:33 +08:00
parent d4b53e23b6
commit 127306a019
13 changed files with 344 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

View File

@ -155,6 +155,25 @@ class KeywordExtract:
quest_keywords = [self.text_map[lang].find(quest_hash)[1] for quest_hash in quests_hash] quest_keywords = [self.text_map[lang].find(quest_hash)[1] for quest_hash in quests_hash]
self.load_keywords(quest_keywords, lang) self.load_keywords(quest_keywords, lang)
def generate_forgotten_hall_stages(self):
keyword_class = "ForgottenHallStage"
output_file = './tasks/forgotten_hall/keywords/stage.py'
gen = CodeGenerator()
gen.Import(f"""
from .classes import {keyword_class}
""")
gen.CommentAutoGenerage('dev_tools.keyword_extract')
for stage_id in range(1, 16):
id_str = str(stage_id).rjust(2, '0')
with gen.Object(key=f"Stage_{stage_id}", object_class=keyword_class):
gen.ObjectAttr(key='id', value=stage_id)
gen.ObjectAttr(key='name', value=id_str)
for lang in UI_LANGUAGES:
gen.ObjectAttr(key=lang, value=id_str)
print(f'Write {output_file}')
gen.write(output_file)
def generate_assignment_keywords(self): def generate_assignment_keywords(self):
KeywordFromFile = namedtuple('KeywordFromFile', ('file', 'class_name', 'output_file')) KeywordFromFile = namedtuple('KeywordFromFile', ('file', 'class_name', 'output_file'))
for keyword in ( for keyword in (
@ -182,6 +201,7 @@ class KeywordExtract:
self.load_keywords(['奖励', '任务']) self.load_keywords(['奖励', '任务'])
self.write_keywords(keyword_class='BattlePassTab', output_file='./tasks/battle_pass/keywords/tab.py') self.write_keywords(keyword_class='BattlePassTab', output_file='./tasks/battle_pass/keywords/tab.py')
self.generate_assignment_keywords() self.generate_assignment_keywords()
self.generate_forgotten_hall_stages()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -154,6 +154,20 @@ class Ocr:
text=str(result)) text=str(result))
return result return result
def ocr_multi_lines(self, image_list):
# pre process
start_time = time.time()
image_list = [self.pre_process(image) for image in image_list]
# ocr
result_list = self.model.ocr_lines(image_list)
result_list = [(result, score) for result, score in result_list]
# after process
result_list = [(self.after_process(result), score) for result, score in result_list]
result_list = [(self.format_result(result), score) for result, score in result_list]
logger.attr(name="%s %ss" % (self.name, float2str(time.time() - start_time)),
text=str([result for result, _ in result_list]))
return result_list
def detect_and_ocr(self, image, direct_ocr=False) -> list[BoxedResult]: def detect_and_ocr(self, image, direct_ocr=False) -> list[BoxedResult]:
""" """
Args: Args:

View File

@ -29,13 +29,15 @@ class DraggableList:
ocr_class, ocr_class,
search_button: ButtonWrapper, search_button: ButtonWrapper,
check_row_order: bool = True, check_row_order: bool = True,
active_color: tuple[int, int, int] = (190, 175, 124) active_color: tuple[int, int, int] = (190, 175, 124),
drag_direction: str = "down"
): ):
""" """
Args: Args:
name: name:
keyword_class: Keyword keyword_class: Keyword
search_button: search_button:
drag_direction: Default drag direction to higher index
""" """
self.name = name self.name = name
self.keyword_class = keyword_class self.keyword_class = keyword_class
@ -46,6 +48,7 @@ class DraggableList:
self.search_button = search_button self.search_button = search_button
self.check_row_order = check_row_order self.check_row_order = check_row_order
self.active_color = active_color self.active_color = active_color
self.drag_direction = drag_direction
self.row_min = 1 self.row_min = 1
self.row_max = len(self.known_rows) self.row_max = len(self.known_rows)
@ -129,6 +132,16 @@ class DraggableList:
p1, p2 = random_rectangle_vector_opted(vector, box=self.search_button.button) p1, p2 = random_rectangle_vector_opted(vector, box=self.search_button.button)
main.device.drag(p1, p2, name=f'{self.name}_DRAG') main.device.drag(p1, p2, name=f'{self.name}_DRAG')
def reverse_direction(self, direction):
if direction == 'up':
return 'down'
if direction == 'down':
return 'up'
if direction == 'left':
return 'right'
if direction == 'right':
return 'left'
def insight_row(self, row: Keyword, main: ModuleBase, skip_first_screenshot=True) -> bool: def insight_row(self, row: Keyword, main: ModuleBase, skip_first_screenshot=True) -> bool:
""" """
Args: Args:
@ -159,9 +172,10 @@ class DraggableList:
# Drag pages # Drag pages
if row_index < self.cur_min: if row_index < self.cur_min:
self.drag_page('up', main=main) self.drag_page(self.reverse_direction(self.drag_direction), main=main)
elif self.cur_max < row_index: elif self.cur_max < row_index:
self.drag_page('down', main=main) self.drag_page(self.drag_direction, main=main)
# Wait for bottoming out # Wait for bottoming out
main.wait_until_stable(self.search_button, timer=Timer( main.wait_until_stable(self.search_button, timer=Timer(
0.3, count=1), timeout=Timer(1.5, count=5)) 0.3, count=1), timeout=Timer(1.5, count=5))

View File

@ -46,6 +46,10 @@ class DungeonList(Keyword):
def is_Forgotten_Hall(self): def is_Forgotten_Hall(self):
return 'Forgotten_Hall' or 'Last_Vestiges' in self.name return 'Forgotten_Hall' or 'Last_Vestiges' in self.name
@property
def is_Last_Vestiges(self):
return 'Last_Vestiges' in self.name
@property @property
def is_daily_dungeon(self): def is_daily_dungeon(self):
return self.is_Calyx_Golden or self.is_Calyx_Crimson or self.is_Stagnant_Shadow or self.is_Cavern_of_Corrosion return self.is_Calyx_Golden or self.is_Calyx_Crimson or self.is_Stagnant_Shadow or self.is_Cavern_of_Corrosion

View File

@ -0,0 +1,35 @@
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 ```
ENTRANCE_CHECKED = ButtonWrapper(
name='ENTRANCE_CHECKED',
share=Button(
file='./assets/share/forgotten_hall/ENTRANCE_CHECKED.png',
area=(62, 662, 80, 680),
search=(42, 642, 100, 700),
color=(160, 162, 162),
button=(62, 662, 80, 680),
),
)
LAST_VERTIGES = ButtonWrapper(
name='LAST_VERTIGES',
share=Button(
file='./assets/share/forgotten_hall/LAST_VERTIGES.png',
area=(1163, 105, 1203, 125),
search=(1143, 85, 1223, 145),
color=(221, 185, 225),
button=(1163, 105, 1203, 125),
),
)
OCR_STAGE = ButtonWrapper(
name='OCR_STAGE',
share=Button(
file='./assets/share/forgotten_hall/OCR_STAGE.png',
area=(0, 281, 1280, 581),
search=(0, 261, 1280, 601),
color=(29, 48, 92),
button=(0, 0, 1000, 100),
),
)

View File

@ -0,0 +1,2 @@
import tasks.forgotten_hall.keywords.stage as KEYWORDS_FORGOTTEN_HALL_STAGE
from tasks.forgotten_hall.keywords.classes import ForgottenHallStage

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass
from typing import ClassVar
from module.ocr.keyword import Keyword
@dataclass(repr=False)
class ForgottenHallStage(Keyword):
instances: ClassVar = {}

View File

@ -0,0 +1,125 @@
from .classes import ForgottenHallStage
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Stage_1 = ForgottenHallStage(
id=1,
name='01',
cn='01',
cht='01',
en='01',
jp='01',
)
Stage_2 = ForgottenHallStage(
id=2,
name='02',
cn='02',
cht='02',
en='02',
jp='02',
)
Stage_3 = ForgottenHallStage(
id=3,
name='03',
cn='03',
cht='03',
en='03',
jp='03',
)
Stage_4 = ForgottenHallStage(
id=4,
name='04',
cn='04',
cht='04',
en='04',
jp='04',
)
Stage_5 = ForgottenHallStage(
id=5,
name='05',
cn='05',
cht='05',
en='05',
jp='05',
)
Stage_6 = ForgottenHallStage(
id=6,
name='06',
cn='06',
cht='06',
en='06',
jp='06',
)
Stage_7 = ForgottenHallStage(
id=7,
name='07',
cn='07',
cht='07',
en='07',
jp='07',
)
Stage_8 = ForgottenHallStage(
id=8,
name='08',
cn='08',
cht='08',
en='08',
jp='08',
)
Stage_9 = ForgottenHallStage(
id=9,
name='09',
cn='09',
cht='09',
en='09',
jp='09',
)
Stage_10 = ForgottenHallStage(
id=10,
name='10',
cn='10',
cht='10',
en='10',
jp='10',
)
Stage_11 = ForgottenHallStage(
id=11,
name='11',
cn='11',
cht='11',
en='11',
jp='11',
)
Stage_12 = ForgottenHallStage(
id=12,
name='12',
cn='12',
cht='12',
en='12',
jp='12',
)
Stage_13 = ForgottenHallStage(
id=13,
name='13',
cn='13',
cht='13',
en='13',
jp='13',
)
Stage_14 = ForgottenHallStage(
id=14,
name='14',
cn='14',
cht='14',
en='14',
jp='14',
)
Stage_15 = ForgottenHallStage(
id=15,
name='15',
cn='15',
cht='15',
en='15',
jp='15',
)

118
tasks/forgotten_hall/ui.py Normal file
View File

@ -0,0 +1,118 @@
import cv2
import numpy as np
from ppocronnx.predict_system import BoxedResult
from module.base.base import ModuleBase
from module.base.utils import area_offset, color_similarity_2d, crop
from module.logger.logger import logger
from module.ocr.keyword import Keyword
from module.ocr.ocr import Ocr, OcrResultButton
from module.ui.draggable_list import DraggableList
from tasks.base.assets.assets_base_page import FORGOTTEN_HALL_CHECK
from tasks.base.ui import UI
from tasks.dungeon.keywords import DungeonList
from tasks.dungeon.ui import DungeonUI
from tasks.forgotten_hall.assets.assets_forgotten_hall import *
from tasks.forgotten_hall.keywords import *
class ForgottenHallStageOcr(Ocr):
def _find_number(self, image):
raw = image.copy()
area = OCR_STAGE.area
image = crop(raw, area)
yellow = color_similarity_2d(image, color=(250, 201, 111))
gray = color_similarity_2d(image, color=(100, 109, 134))
image = np.maximum(yellow, gray)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 3))
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
_, image = cv2.threshold(image, 220, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
rectangle = []
for cont in contours:
rect = cv2.boundingRect(cv2.convexHull(cont).astype(np.float32))
# Filter with rectangle width, usually to be 62~64
if not 62 - 10 < rect[2] < 62 + 10:
continue
rect = (rect[0], rect[1], rect[0] + rect[2], rect[1] + rect[3])
rect = area_offset(rect, offset=area[:2])
# Move from stars to letters
rect = area_offset((-10, -55, 50, -15), offset=rect[:2])
rectangle.append(rect)
return rectangle
def matched_ocr(self, image, keyword_classes, direct_ocr=False) -> list[OcrResultButton]:
if not isinstance(keyword_classes, list):
keyword_classes = [keyword_classes]
boxes = self._find_number(image)
image_list = [crop(image, area) for area in boxes]
results = self.ocr_multi_lines(image_list)
boxed_results = [
BoxedResult(area_offset(boxes[index], (-50, 0)), image_list[index], text, score)
for index, (text, score) in enumerate(results)
]
results_buttons = [
OcrResultButton(result, keyword_classes)
for result in boxed_results
]
logger.attr(name=f'{self.name} matched', text=results_buttons)
return results_buttons
class DraggableStageList(DraggableList):
def insight_row(self, row: Keyword, main: ModuleBase, skip_first_screenshot=True) -> bool:
while 1:
result = super().insight_row(row, main=main, skip_first_screenshot=skip_first_screenshot)
if not result:
return False
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
button = self.keyword2button(row)
# end
if button.button[0] > 0:
break
# Stage number is insight but button is not
logger.info("Stage number is insight, swipe left a little bit to find the entrance")
self.drag_vector = (0.2, 0.4)
self.drag_page("left", main=main)
self.drag_vector = DraggableList.drag_vector
return True
def is_row_selected(self, button: OcrResultButton, main: ModuleBase) -> bool:
return main.appear(ENTRANCE_CHECKED)
STAGE_LIST = DraggableStageList("ForgottenHallStageList", keyword_class=ForgottenHallStage,
ocr_class=ForgottenHallStageOcr, search_button=OCR_STAGE,
check_row_order=False, drag_direction="right")
class ForgottenHallUI(UI):
def stage_goto(self, forgotten_hall: DungeonList, stage_keyword: ForgottenHallStage):
"""
Examples:
self = ForgottenHallUI('alas')
self.device.screenshot()
self.stage_goto(KEYWORDS_DUNGEON_LIST.The_Last_Vestiges_of_Towering_Citadel,
KEYWORDS_FORGOTTEN_HALL_STAGE.Stage_8)
"""
if not forgotten_hall.is_Forgotten_Hall:
logger.warning("DungeonList Chosen is not a forgotten hall")
return
if not forgotten_hall.is_Last_Vestiges and stage_keyword.id > 10:
logger.warning(f"This dungeon does not have stage that greater than 10. {stage_keyword.id} is chosen")
return
if not self.appear(FORGOTTEN_HALL_CHECK):
dungeon_ui = DungeonUI(config=self.config, device=self.device)
dungeon_ui.dungeon_goto(forgotten_hall)
STAGE_LIST.select_row(stage_keyword, main=self)