Add: Enter rogue

This commit is contained in:
LmeSzinc 2023-10-18 21:01:48 +08:00
parent c3a1a5720e
commit 8da4644e13
15 changed files with 366 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -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 = ''

View 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 |

View File

@ -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)

View File

@ -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)

View File

@ -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

View 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
View 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)

View File

@ -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()

View File

@ -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')