mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-25 10:01:10 +00:00
Add: forgotten hall stage goto
This commit is contained in:
parent
d4b53e23b6
commit
127306a019
BIN
assets/share/forgotten_hall/ENTRANCE_CHECKED.png
Normal file
BIN
assets/share/forgotten_hall/ENTRANCE_CHECKED.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/share/forgotten_hall/LAST_VERTIGES.png
Normal file
BIN
assets/share/forgotten_hall/LAST_VERTIGES.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
assets/share/forgotten_hall/OCR_STAGE.BUTTON.png
Normal file
BIN
assets/share/forgotten_hall/OCR_STAGE.BUTTON.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
BIN
assets/share/forgotten_hall/OCR_STAGE.png
Normal file
BIN
assets/share/forgotten_hall/OCR_STAGE.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 KiB |
@ -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__':
|
||||||
|
@ -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:
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
35
tasks/forgotten_hall/assets/assets_forgotten_hall.py
Normal file
35
tasks/forgotten_hall/assets/assets_forgotten_hall.py
Normal 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),
|
||||||
|
),
|
||||||
|
)
|
2
tasks/forgotten_hall/keywords/__init__.py
Normal file
2
tasks/forgotten_hall/keywords/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import tasks.forgotten_hall.keywords.stage as KEYWORDS_FORGOTTEN_HALL_STAGE
|
||||||
|
from tasks.forgotten_hall.keywords.classes import ForgottenHallStage
|
9
tasks/forgotten_hall/keywords/classes.py
Normal file
9
tasks/forgotten_hall/keywords/classes.py
Normal 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 = {}
|
125
tasks/forgotten_hall/keywords/stage.py
Normal file
125
tasks/forgotten_hall/keywords/stage.py
Normal 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
118
tasks/forgotten_hall/ui.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user