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]
|
||||
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):
|
||||
KeywordFromFile = namedtuple('KeywordFromFile', ('file', 'class_name', 'output_file'))
|
||||
for keyword in (
|
||||
@ -182,6 +201,7 @@ class KeywordExtract:
|
||||
self.load_keywords(['奖励', '任务'])
|
||||
self.write_keywords(keyword_class='BattlePassTab', output_file='./tasks/battle_pass/keywords/tab.py')
|
||||
self.generate_assignment_keywords()
|
||||
self.generate_forgotten_hall_stages()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -154,6 +154,20 @@ class Ocr:
|
||||
text=str(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]:
|
||||
"""
|
||||
Args:
|
||||
|
@ -29,13 +29,15 @@ class DraggableList:
|
||||
ocr_class,
|
||||
search_button: ButtonWrapper,
|
||||
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:
|
||||
name:
|
||||
keyword_class: Keyword
|
||||
search_button:
|
||||
drag_direction: Default drag direction to higher index
|
||||
"""
|
||||
self.name = name
|
||||
self.keyword_class = keyword_class
|
||||
@ -46,6 +48,7 @@ class DraggableList:
|
||||
self.search_button = search_button
|
||||
self.check_row_order = check_row_order
|
||||
self.active_color = active_color
|
||||
self.drag_direction = drag_direction
|
||||
|
||||
self.row_min = 1
|
||||
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)
|
||||
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:
|
||||
"""
|
||||
Args:
|
||||
@ -159,9 +172,10 @@ class DraggableList:
|
||||
|
||||
# Drag pages
|
||||
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:
|
||||
self.drag_page('down', main=main)
|
||||
self.drag_page(self.drag_direction, main=main)
|
||||
|
||||
# Wait for bottoming out
|
||||
main.wait_until_stable(self.search_button, timer=Timer(
|
||||
0.3, count=1), timeout=Timer(1.5, count=5))
|
||||
|
@ -46,6 +46,10 @@ class DungeonList(Keyword):
|
||||
def is_Forgotten_Hall(self):
|
||||
return 'Forgotten_Hall' or 'Last_Vestiges' in self.name
|
||||
|
||||
@property
|
||||
def is_Last_Vestiges(self):
|
||||
return 'Last_Vestiges' in self.name
|
||||
|
||||
@property
|
||||
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
|
||||
|
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