mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-16 06:25:24 +00:00
Add: Enter rogue
This commit is contained in:
parent
c3a1a5720e
commit
8da4644e13
BIN
assets/share/rogue/entry/LEVEL_CONFIRM.BUTTON.png
Normal file
BIN
assets/share/rogue/entry/LEVEL_CONFIRM.BUTTON.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
assets/share/rogue/entry/LEVEL_CONFIRM.png
Normal file
BIN
assets/share/rogue/entry/LEVEL_CONFIRM.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
assets/share/rogue/entry/OCR_WORLD.png
Normal file
BIN
assets/share/rogue/entry/OCR_WORLD.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
assets/share/rogue/entry/WORLD_ENTER.png
Normal file
BIN
assets/share/rogue/entry/WORLD_ENTER.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
assets/share/rogue/entry/WORLD_NEXT.png
Normal file
BIN
assets/share/rogue/entry/WORLD_NEXT.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
assets/share/rogue/entry/WORLD_PREV.png
Normal file
BIN
assets/share/rogue/entry/WORLD_PREV.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -213,6 +213,25 @@ class ModuleBase:
|
||||
button_area = area_offset((-encourage, -encourage, encourage, encourage), offset=point)
|
||||
return ClickButton(area=button_area, name=name)
|
||||
|
||||
def get_interval_timer(self, button, interval=5) -> Timer:
|
||||
if hasattr(button, 'name'):
|
||||
name = button.name
|
||||
elif callable(button):
|
||||
name = button.__name__
|
||||
else:
|
||||
name = str(button)
|
||||
|
||||
try:
|
||||
timer = self.interval_timer[name]
|
||||
if timer.limit != interval:
|
||||
timer = Timer(interval)
|
||||
self.interval_timer[name] = timer
|
||||
return timer
|
||||
except KeyError:
|
||||
timer = Timer(interval)
|
||||
self.interval_timer[name] = timer
|
||||
return timer
|
||||
|
||||
def interval_reset(self, button, interval=5):
|
||||
if isinstance(button, (list, tuple)):
|
||||
for b in button:
|
||||
@ -220,10 +239,7 @@ class ModuleBase:
|
||||
return
|
||||
|
||||
if button is not None:
|
||||
if button.name in self.interval_timer:
|
||||
self.interval_timer[button.name].reset()
|
||||
else:
|
||||
self.interval_timer[button.name] = Timer(interval).reset()
|
||||
self.get_interval_timer(button, interval=interval).reset()
|
||||
|
||||
def interval_clear(self, button, interval=5):
|
||||
if isinstance(button, (list, tuple)):
|
||||
@ -232,19 +248,10 @@ class ModuleBase:
|
||||
return
|
||||
|
||||
if button is not None:
|
||||
if button.name in self.interval_timer:
|
||||
self.interval_timer[button.name].clear()
|
||||
else:
|
||||
self.interval_timer[button.name] = Timer(interval).clear()
|
||||
self.get_interval_timer(button, interval=interval).clear()
|
||||
|
||||
def interval_is_reached(self, button, interval=5):
|
||||
if button.name in self.interval_timer:
|
||||
if self.interval_timer[button.name].limit != interval:
|
||||
self.interval_timer[button.name] = Timer(interval)
|
||||
else:
|
||||
self.interval_timer[button.name] = Timer(interval)
|
||||
|
||||
return self.interval_timer[button.name].reached()
|
||||
return self.get_interval_timer(button, interval=interval).reached()
|
||||
|
||||
_image_file = ''
|
||||
|
||||
|
@ -5,6 +5,11 @@ from tasks.rogue.route.base import RouteBase
|
||||
|
||||
class Route(RouteBase):
|
||||
|
||||
def map_init(self, *args, **kwargs):
|
||||
super().map_init(*args, **kwargs)
|
||||
# Blue triangle on blue sky, high error rate
|
||||
self.minimap.lock_rotation(0)
|
||||
|
||||
def Luofu_Cloudford_F1_X337Y1003(self):
|
||||
"""
|
||||
| Waypoint | Position | Direction | Rotation |
|
||||
|
@ -33,7 +33,9 @@ class OcrPlaneName(Ocr):
|
||||
# Domain-Qccurrence
|
||||
result = result.replace('cunr', 'cur').replace('uren', 'urren').replace('Qcc', 'Occ')
|
||||
# Domain-Elit
|
||||
# Domain--Etite
|
||||
result = re.sub(r'[Ee]lit$', 'Elite', result)
|
||||
result = result.replace('tite', 'lite')
|
||||
|
||||
# 区域-战
|
||||
result = re.sub(r'区域.*战$', '区域战斗', result)
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Callable
|
||||
|
||||
from module.base.base import ModuleBase
|
||||
from module.logger import logger
|
||||
from tasks.base.assets.assets_base_page import BACK, CLOSE
|
||||
@ -98,7 +100,7 @@ class PopupHandler(ModuleBase):
|
||||
|
||||
return False
|
||||
|
||||
def handle_ui_close(self, appear_button: ButtonWrapper, interval=2) -> bool:
|
||||
def handle_ui_close(self, appear_button: ButtonWrapper | Callable, interval=2) -> bool:
|
||||
"""
|
||||
Args:
|
||||
appear_button: Click if button appears
|
||||
@ -107,6 +109,13 @@ class PopupHandler(ModuleBase):
|
||||
Returns:
|
||||
If handled.
|
||||
"""
|
||||
if callable(appear_button):
|
||||
if self.interval_is_reached(appear_button) and appear_button():
|
||||
logger.info(f'{appear_button.__name__} -> {CLOSE}')
|
||||
self.device.click(CLOSE)
|
||||
self.interval_reset(appear_button)
|
||||
return True
|
||||
else:
|
||||
if self.appear(appear_button, interval=interval):
|
||||
logger.info(f'{appear_button} -> {CLOSE}')
|
||||
self.device.click(CLOSE)
|
||||
@ -114,7 +123,7 @@ class PopupHandler(ModuleBase):
|
||||
|
||||
return False
|
||||
|
||||
def handle_ui_back(self, appear_button: ButtonWrapper, interval=2) -> bool:
|
||||
def handle_ui_back(self, appear_button: ButtonWrapper | Callable, interval=2) -> bool:
|
||||
"""
|
||||
Args:
|
||||
appear_button: Click if button appears
|
||||
@ -123,6 +132,13 @@ class PopupHandler(ModuleBase):
|
||||
Returns:
|
||||
If handled.
|
||||
"""
|
||||
if callable(appear_button):
|
||||
if self.interval_is_reached(appear_button) and appear_button():
|
||||
logger.info(f'{appear_button.__name__} -> {BACK}')
|
||||
self.device.click(BACK)
|
||||
self.interval_reset(appear_button)
|
||||
return True
|
||||
else:
|
||||
if self.appear(appear_button, interval=interval):
|
||||
logger.info(f'{appear_button} -> {BACK}')
|
||||
self.device.click(BACK)
|
||||
|
@ -421,12 +421,6 @@ class DungeonUI(UI):
|
||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
self.dungeon_goto(KEYWORDS_DUNGEON_LIST.Calyx_Crimson_Harmony)
|
||||
"""
|
||||
if dungeon.is_Simulated_Universe:
|
||||
self._dungeon_nav_goto(dungeon)
|
||||
pass
|
||||
self._dungeon_insight(dungeon)
|
||||
return True
|
||||
|
||||
# Reset search button
|
||||
DUNGEON_LIST.search_button = OCR_DUNGEON_LIST
|
||||
|
||||
|
55
tasks/rogue/assets/assets_rogue_entry.py
Normal file
55
tasks/rogue/assets/assets_rogue_entry.py
Normal file
@ -0,0 +1,55 @@
|
||||
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 ```
|
||||
|
||||
LEVEL_CONFIRM = ButtonWrapper(
|
||||
name='LEVEL_CONFIRM',
|
||||
share=Button(
|
||||
file='./assets/share/rogue/entry/LEVEL_CONFIRM.png',
|
||||
area=(1224, 633, 1248, 681),
|
||||
search=(1204, 613, 1268, 701),
|
||||
color=(179, 178, 182),
|
||||
button=(990, 638, 1223, 676),
|
||||
),
|
||||
)
|
||||
OCR_WORLD = ButtonWrapper(
|
||||
name='OCR_WORLD',
|
||||
share=Button(
|
||||
file='./assets/share/rogue/entry/OCR_WORLD.png',
|
||||
area=(500, 362, 700, 390),
|
||||
search=(480, 342, 720, 410),
|
||||
color=(51, 51, 62),
|
||||
button=(500, 362, 700, 390),
|
||||
),
|
||||
)
|
||||
WORLD_ENTER = ButtonWrapper(
|
||||
name='WORLD_ENTER',
|
||||
share=Button(
|
||||
file='./assets/share/rogue/entry/WORLD_ENTER.png',
|
||||
area=(820, 323, 980, 474),
|
||||
search=(800, 303, 1000, 494),
|
||||
color=(120, 130, 218),
|
||||
button=(820, 323, 980, 474),
|
||||
),
|
||||
)
|
||||
WORLD_NEXT = ButtonWrapper(
|
||||
name='WORLD_NEXT',
|
||||
share=Button(
|
||||
file='./assets/share/rogue/entry/WORLD_NEXT.png',
|
||||
area=(747, 620, 888, 708),
|
||||
search=(727, 600, 908, 720),
|
||||
color=(143, 88, 76),
|
||||
button=(747, 620, 888, 708),
|
||||
),
|
||||
)
|
||||
WORLD_PREV = ButtonWrapper(
|
||||
name='WORLD_PREV',
|
||||
share=Button(
|
||||
file='./assets/share/rogue/entry/WORLD_PREV.png',
|
||||
area=(745, 76, 889, 137),
|
||||
search=(725, 56, 909, 157),
|
||||
color=(49, 125, 128),
|
||||
button=(745, 76, 889, 137),
|
||||
),
|
||||
)
|
251
tasks/rogue/entry/entry.py
Normal file
251
tasks/rogue/entry/entry.py
Normal file
@ -0,0 +1,251 @@
|
||||
import re
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.exception import RequestHumanTakeover
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import Ocr
|
||||
from tasks.base.assets.assets_base_main_page import ROGUE_LEAVE_FOR_NOW
|
||||
from tasks.base.assets.assets_base_page import MAP_EXIT
|
||||
from tasks.base.page import page_guide, page_main, page_rogue
|
||||
from tasks.dungeon.keywords import DungeonList
|
||||
from tasks.dungeon.keywords.dungeon import Simulated_Universe_World_1
|
||||
from tasks.dungeon.keywords.tab import Survival_Index
|
||||
from tasks.dungeon.ui import DungeonUI
|
||||
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT
|
||||
from tasks.rogue.assets.assets_rogue_entry import LEVEL_CONFIRM, OCR_WORLD, WORLD_ENTER, WORLD_NEXT, WORLD_PREV
|
||||
from tasks.rogue.assets.assets_rogue_path import CONFIRM_PATH
|
||||
from tasks.rogue.assets.assets_rogue_reward import REWARD_CLOSE, REWARD_ENTER
|
||||
from tasks.rogue.assets.assets_rogue_ui import ROGUE_LAUNCH
|
||||
from tasks.rogue.entry.path import RoguePathHandler
|
||||
from tasks.rogue.entry.reward import RogueRewardHandler
|
||||
|
||||
|
||||
def chinese_to_arabic(chinese_number: str) -> int:
|
||||
chinese_numerals = {
|
||||
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5,
|
||||
'六': 6, '七': 7, '八': 8, '九': 9, '零': 0
|
||||
}
|
||||
result = 0
|
||||
current_number = 0
|
||||
|
||||
for char in chinese_number:
|
||||
if char in chinese_numerals:
|
||||
# If the character is a valid Chinese numeral, accumulate the value.
|
||||
current_number = chinese_numerals[char]
|
||||
else:
|
||||
# If it's not a valid Chinese numeral, assume it's a multiplier (e.g., 十 for 10).
|
||||
multiplier = 1
|
||||
if char == '十':
|
||||
multiplier = 10
|
||||
elif char == '百':
|
||||
multiplier = 100
|
||||
elif char == '千':
|
||||
multiplier = 1000
|
||||
elif char == '万':
|
||||
result += current_number * 10000
|
||||
current_number = 0
|
||||
continue
|
||||
|
||||
result += current_number * multiplier
|
||||
current_number = 0
|
||||
|
||||
result += current_number # Add the last accumulated number.
|
||||
return result
|
||||
|
||||
|
||||
class OcrRogueWorld(Ocr):
|
||||
def format_result(self, result: str) -> int:
|
||||
res = re.search(r'第([一二三四五六七八九十])世界', result)
|
||||
if res:
|
||||
return chinese_to_arabic(res.group(1))
|
||||
res = re.search(r'world[\s_]?(\d)', result.lower())
|
||||
if res:
|
||||
return int(res.group(1))
|
||||
return 0
|
||||
|
||||
|
||||
class RogueEntry(DungeonUI, RogueRewardHandler, RoguePathHandler):
|
||||
def _rogue_world_set(self, world: int | DungeonList, skip_first_screenshot=True):
|
||||
"""
|
||||
Args:
|
||||
world: 7 or KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_7
|
||||
skip_first_screenshot:
|
||||
|
||||
Pages:
|
||||
in: is_page_rogue_main()
|
||||
"""
|
||||
ocr = OcrRogueWorld(OCR_WORLD)
|
||||
if isinstance(world, DungeonList):
|
||||
w = ocr.format_result(world.en)
|
||||
if w:
|
||||
world = w
|
||||
else:
|
||||
logger.error(f'Invalid rogue world: {world}')
|
||||
raise RequestHumanTakeover
|
||||
|
||||
logger.info(f'Rogue world set: {world}')
|
||||
interval = Timer(1, count=3)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# Additional
|
||||
if self.appear_then_click(REWARD_CLOSE, interval=2):
|
||||
continue
|
||||
if self.handle_ui_close(LEVEL_CONFIRM, interval=2):
|
||||
interval.clear()
|
||||
continue
|
||||
|
||||
if self.is_page_rogue_main() \
|
||||
and self.image_color_count(OCR_WORLD, color=(255, 255, 255), threshold=221, count=50):
|
||||
# End
|
||||
current = ocr.ocr_single_line(self.device.image)
|
||||
if current and current == world:
|
||||
logger.info(f'At world {world}')
|
||||
break
|
||||
# Click
|
||||
if interval.reached():
|
||||
diff = world - current
|
||||
if diff > 0:
|
||||
# 0.5s at min
|
||||
self.device.multi_click(WORLD_NEXT, n=abs(diff), interval=(0.5, 0.7))
|
||||
interval.reset()
|
||||
elif diff < 0:
|
||||
self.device.multi_click(WORLD_PREV, n=abs(diff), interval=(0.5, 0.7))
|
||||
interval.reset()
|
||||
else:
|
||||
logger.error(f'Invalid world diff: {diff}')
|
||||
|
||||
def _rogue_world_enter(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Pages:
|
||||
in: is_page_rogue_main()
|
||||
out: is_page_rogue_launch()
|
||||
"""
|
||||
logger.info('Rogue world enter')
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# End
|
||||
if self.is_page_rogue_launch():
|
||||
logger.info('At is_page_rogue_launch()')
|
||||
break
|
||||
|
||||
# Click
|
||||
if self.interval_is_reached(REWARD_ENTER, interval=2) and self.is_page_rogue_main():
|
||||
self.device.click(WORLD_ENTER)
|
||||
self.interval_reset(REWARD_ENTER, interval=2)
|
||||
continue
|
||||
if self.appear_then_click(LEVEL_CONFIRM, interval=2):
|
||||
continue
|
||||
if self.appear_then_click(REWARD_CLOSE, interval=2):
|
||||
continue
|
||||
|
||||
def rogue_world_exit(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Pages:
|
||||
in: is_page_rogue_launch()
|
||||
out: is_page_rogue_main()
|
||||
"""
|
||||
logger.info('Rogue world exit')
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# End
|
||||
if self.is_page_rogue_main():
|
||||
logger.info('At is_page_rogue_main()')
|
||||
break
|
||||
|
||||
# Click
|
||||
# From _rogue_world_enter()
|
||||
if self.handle_ui_close(ROGUE_LAUNCH):
|
||||
continue
|
||||
if self.handle_ui_back(LEVEL_CONFIRM):
|
||||
continue
|
||||
if self.appear_then_click(REWARD_CLOSE, interval=2):
|
||||
continue
|
||||
# From _is_page_rogue_path()
|
||||
if self.handle_ui_back(CONFIRM_PATH):
|
||||
pass
|
||||
if self.handle_ui_back(self._is_page_rogue_path):
|
||||
continue
|
||||
# From ui_leave_special()
|
||||
if self.appear_then_click(MAP_EXIT, interval=2):
|
||||
continue
|
||||
if self.handle_popup_confirm():
|
||||
continue
|
||||
if self.appear_then_click(ROGUE_LEAVE_FOR_NOW, interval=2):
|
||||
continue
|
||||
|
||||
def _rogue_teleport(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Pages:
|
||||
in: page_guide, Survival_Index, Simulated_Universe
|
||||
out: page_rogue
|
||||
"""
|
||||
logger.info('Rogue teleport')
|
||||
self.interval_clear(page_guide.check_button)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# End
|
||||
if self.ui_page_appear(page_rogue):
|
||||
break
|
||||
|
||||
if self.appear_then_click(REWARD_CLOSE, interval=2):
|
||||
continue
|
||||
if self.appear(page_guide.check_button, interval=2):
|
||||
buttons = TELEPORT.match_multi_template(self.device.image)
|
||||
if len(buttons):
|
||||
buttons = sorted(buttons, key=lambda x: x.area[1])
|
||||
self.device.click(buttons[0])
|
||||
continue
|
||||
|
||||
self.interval_clear(page_guide.check_button)
|
||||
|
||||
def goto_rogue(self):
|
||||
self.dungeon_tab_goto(Survival_Index)
|
||||
self._dungeon_nav_goto(Simulated_Universe_World_1)
|
||||
self._rogue_teleport()
|
||||
|
||||
def rogue_world_enter(self, world: int | DungeonList):
|
||||
"""
|
||||
Args:
|
||||
world: 7 or KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_7
|
||||
|
||||
Pages:
|
||||
in: page_rogue
|
||||
out: is_page_rogue_launch()
|
||||
"""
|
||||
logger.hr('Rogue world enter', level=1)
|
||||
current = self.ui_get_current_page()
|
||||
if current == page_rogue:
|
||||
if self.is_page_rogue_main():
|
||||
logger.info('At is_page_rogue_main()')
|
||||
else:
|
||||
self.rogue_world_exit()
|
||||
elif current == page_main:
|
||||
self.handle_lang_check(page=page_main)
|
||||
if self.plane.rogue_domain:
|
||||
logger.info('At rogue domain')
|
||||
return
|
||||
else:
|
||||
self.goto_rogue()
|
||||
else:
|
||||
self.goto_rogue()
|
||||
|
||||
# Enter
|
||||
self._rogue_world_set(world)
|
||||
self._rogue_world_enter()
|
||||
self.rogue_path_select(self.config.RoguePath_Path)
|
@ -1,18 +1,17 @@
|
||||
from tasks.rogue.entry.path import RoguePathHandler
|
||||
from tasks.rogue.entry.reward import RogueRewardHandler
|
||||
from tasks.rogue.entry.entry import RogueEntry
|
||||
from tasks.rogue.route.loader import RouteLoader
|
||||
|
||||
|
||||
class RogueHandler(RouteLoader, RogueRewardHandler, RoguePathHandler):
|
||||
class RogueHandler(RouteLoader, RogueEntry):
|
||||
def rogue_once(self):
|
||||
"""
|
||||
Do a complete rogue run.
|
||||
|
||||
Pages:
|
||||
in: page_rogue, is_page_rogue_main()
|
||||
in: Any
|
||||
out: page_rogue, is_page_rogue_main()
|
||||
"""
|
||||
self.rogue_path_select(self.config.RoguePath_Path)
|
||||
self.rogue_world_enter(7)
|
||||
self.rogue_run()
|
||||
self.rogue_reward_claim()
|
||||
|
||||
|
@ -135,6 +135,7 @@ class RouteLoader(RogueUI, MinimapWrapper, RouteLoader_, MainPage):
|
||||
route = nearby[0][0]
|
||||
else:
|
||||
route = visited[0][0]
|
||||
logger.attr('RoutePredict', route.name)
|
||||
return route
|
||||
else:
|
||||
logger.warning('Similarity too close, not enough to make a prediction')
|
||||
|
Loading…
Reference in New Issue
Block a user