mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-16 14:31:16 +00:00
152 lines
5.3 KiB
Python
152 lines
5.3 KiB
Python
|
from typing import Optional
|
||
|
|
||
|
from module.base.decorator import cached_property
|
||
|
from module.logger import logger
|
||
|
from tasks.base.main_page import MainPage
|
||
|
from tasks.map.keywords import MapPlane
|
||
|
from tasks.map.keywords.plane import (
|
||
|
Herta_MasterControlZone,
|
||
|
Herta_ParlorCar,
|
||
|
Jarilo_AdministrativeDistrict,
|
||
|
Luofu_AurumAlley,
|
||
|
Luofu_ExaltingSanctum
|
||
|
)
|
||
|
from tasks.map.minimap.minimap import Minimap
|
||
|
from tasks.map.route.loader import RouteLoader as RouteLoader_
|
||
|
from tasks.rogue.route.base import RouteBase
|
||
|
from tasks.rogue.route.model import RogueRouteListModel, RogueRouteModel
|
||
|
|
||
|
|
||
|
def model_from_json(model, file: str):
|
||
|
with open(file, 'r', encoding='utf-8') as f:
|
||
|
content = f.read()
|
||
|
data = model.model_validate_json(content)
|
||
|
return data
|
||
|
|
||
|
|
||
|
class RouteLoader(RouteLoader_, MainPage):
|
||
|
@cached_property
|
||
|
def all_minimap(self) -> dict[(str, str), Minimap]:
|
||
|
"""
|
||
|
Returns:
|
||
|
dict: Key: {world}_{plane}_{floor}, e.g. Jarilo_SilvermaneGuardRestrictedZone_F1
|
||
|
Value: Minimap object
|
||
|
"""
|
||
|
# No enemy spawn at the followings
|
||
|
blacklist = [
|
||
|
Herta_ParlorCar,
|
||
|
Herta_MasterControlZone,
|
||
|
Jarilo_AdministrativeDistrict,
|
||
|
Luofu_ExaltingSanctum,
|
||
|
Luofu_AurumAlley,
|
||
|
]
|
||
|
maps = {}
|
||
|
for plane in MapPlane.instances.values():
|
||
|
if plane in blacklist:
|
||
|
continue
|
||
|
if not plane.world:
|
||
|
continue
|
||
|
for floor in plane.floors:
|
||
|
minimap = Minimap()
|
||
|
minimap.set_plane(plane=plane, floor=floor)
|
||
|
maps[f'{plane.name}_{floor}'] = minimap
|
||
|
return maps
|
||
|
|
||
|
@cached_property
|
||
|
def all_route(self) -> list[RogueRouteModel]:
|
||
|
return model_from_json(RogueRouteListModel, './route/rogue/route.json').root
|
||
|
|
||
|
def get_minimap(self, route: RogueRouteModel):
|
||
|
return self.all_minimap[route.plane_floor]
|
||
|
|
||
|
def position_find_known(self, image) -> Optional[RogueRouteModel]:
|
||
|
"""
|
||
|
Try to find from known route spawn point
|
||
|
"""
|
||
|
logger.info('position_find_known')
|
||
|
plane = self.get_plane()
|
||
|
if plane is None:
|
||
|
logger.warning('Unknown rogue domain')
|
||
|
return
|
||
|
|
||
|
visited = []
|
||
|
for route in self.all_route:
|
||
|
if plane.rogue_domain and plane.rogue_domain != route.domain:
|
||
|
if plane.rogue_domain == 'Transaction' and route.is_DomainOccurrence:
|
||
|
# Treat "Transaction" as "Occurrence"
|
||
|
pass
|
||
|
elif plane.rogue_domain == 'Encounter' and route.is_DomainOccurrence:
|
||
|
# Treat "Encounter" as "Occurrence"
|
||
|
pass
|
||
|
else:
|
||
|
continue
|
||
|
minimap = self.get_minimap(route)
|
||
|
minimap.init_position(route.position, show_log=False)
|
||
|
try:
|
||
|
minimap.update_position(image)
|
||
|
except FileNotFoundError:
|
||
|
continue
|
||
|
visited.append((route, minimap.position_similarity))
|
||
|
|
||
|
if len(visited) < 3:
|
||
|
logger.warning('Too few routes to search from, not enough to make a prediction')
|
||
|
return
|
||
|
|
||
|
visited = sorted(visited, key=lambda x: x[1], reverse=True)
|
||
|
logger.info(f'Best 3 prediction: {[(r.name, s) for r, s in visited[:3]]}')
|
||
|
if visited[1][1] / visited[0][1] > 0.75:
|
||
|
logger.warning('Similarity too close, not enough to make a prediction')
|
||
|
return
|
||
|
|
||
|
logger.attr('RoutePredict', visited[0][0].name)
|
||
|
return visited[0][0]
|
||
|
|
||
|
def position_find_bruteforce(self, image) -> Minimap:
|
||
|
"""
|
||
|
Fallback method to find from all planes and floors
|
||
|
"""
|
||
|
logger.warning('position_find_bruteforce, this may take a while')
|
||
|
for name, minimap in self.all_minimap.items():
|
||
|
minimap.init_position((0, 0), show_log=False)
|
||
|
try:
|
||
|
minimap.update_position(image)
|
||
|
except FileNotFoundError:
|
||
|
pass
|
||
|
|
||
|
def get_name(minimap_: Minimap) -> str:
|
||
|
return f'{minimap_.plane.name}_{minimap_.floor}_X{int(minimap_.position[0])}Y{int(minimap_.position[1])}'
|
||
|
|
||
|
visited = sorted(self.all_minimap.values(), key=lambda x: x.position_similarity, reverse=True)
|
||
|
logger.info(f'Best 5 prediction: {[(get_name(m), m.position_similarity) for m in visited[:5]]}')
|
||
|
if visited[1].position_similarity / visited[0].position_similarity > 0.75:
|
||
|
logger.warning('Similarity too close, prediction may goes wrong')
|
||
|
|
||
|
logger.attr('RoutePredict', get_name(visited[0]))
|
||
|
return visited[0]
|
||
|
|
||
|
def route_run(self, route=None):
|
||
|
"""
|
||
|
Run a rogue domain
|
||
|
|
||
|
Pages:
|
||
|
in: page_main
|
||
|
out: page_main, at another domain
|
||
|
"""
|
||
|
route = self.position_find_known(self.device.image)
|
||
|
if route is not None:
|
||
|
super().route_run(route)
|
||
|
else:
|
||
|
self.position_find_bruteforce(self.device.image)
|
||
|
logger.error('New route detected, please record it')
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
self = RouteLoader('src', task='Rogue')
|
||
|
# self.image_file = r''
|
||
|
# self.position_find_bruteforce(self.device.image)
|
||
|
|
||
|
self.device.screenshot()
|
||
|
base = RouteBase(config=self.config, device=self.device, task='Rogue')
|
||
|
base.clear_blessing()
|
||
|
self.route_run()
|