mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-26 18:20:42 +00:00
d042f926b5
- Increase EDGE_LINES_HOUGHLINES_THRESHOLD in chapter 3
355 lines
12 KiB
Python
355 lines
12 KiB
Python
import numpy as np
|
|
|
|
from module.exception import PerspectiveError
|
|
from module.handler.info_handler import InfoHandler
|
|
from module.logger import logger
|
|
from module.map.grids import Grids, Grid
|
|
from module.map.map_base import CampaignMap, location2node, location_ensure
|
|
|
|
param_x = np.array([4.41071252e+00, -3.90656142e-03, 1.95849683e+02])
|
|
param_y_positive = np.array([4.88226475e+00, -2.79093133e-03, 1.39520005e+02])
|
|
param_y_negative = np.array([4.58377745e+00, 3.13441976e-04, 1.39028669e+02])
|
|
|
|
|
|
def swipe_multiply_1d(x, a, b, c):
|
|
return a / (x + b) + c
|
|
|
|
|
|
def swipe_multiply_2d(x, y):
|
|
if abs(x) > 0.3:
|
|
x = swipe_multiply_1d(abs(x), *param_x) * x
|
|
else:
|
|
x = x * 200
|
|
|
|
if abs(y) > 0.3:
|
|
y = swipe_multiply_1d(y, *param_y_positive) * y if y > 0 else swipe_multiply_1d(-y, *param_y_negative) * y
|
|
else:
|
|
y = y * 140
|
|
|
|
return x, y
|
|
|
|
|
|
class Camera(InfoHandler):
|
|
map: CampaignMap
|
|
camera = (0, 0)
|
|
|
|
def _map_swipe(self, vector, drop_threshold=0.1):
|
|
"""
|
|
Args:
|
|
vector(tuple, np.ndarray): float
|
|
drop_threshold(float): swipe distance lower than this will be drop, because a closing swipe will be treat
|
|
as a click in game.
|
|
Returns:
|
|
bool: if camera moved.
|
|
"""
|
|
x, y = vector
|
|
if abs(x) > drop_threshold or abs(y) > drop_threshold:
|
|
# Linear fit
|
|
# x = x * 200
|
|
# y = y * 140
|
|
if self.config.CAMERA_SWIPE_MULTIPLY_X is not None and self.config.CAMERA_SWIPE_MULTIPLY_Y is not None:
|
|
if callable(self.config.CAMERA_SWIPE_MULTIPLY_X):
|
|
x = self.config.CAMERA_SWIPE_MULTIPLY_X(x)
|
|
else:
|
|
x = x * self.config.CAMERA_SWIPE_MULTIPLY_X
|
|
if callable(self.config.CAMERA_SWIPE_MULTIPLY_Y):
|
|
y = self.config.CAMERA_SWIPE_MULTIPLY_X(y)
|
|
else:
|
|
y = y * self.config.CAMERA_SWIPE_MULTIPLY_Y
|
|
else:
|
|
# Function fit
|
|
x, y = swipe_multiply_2d(x, y)
|
|
|
|
vector = (-int(x), -int(y))
|
|
self.device.swipe(vector)
|
|
self.device.sleep(0.3)
|
|
self.update()
|
|
else:
|
|
self.update(camera=False)
|
|
|
|
def map_swipe(self, vector):
|
|
"""
|
|
Swipe to a grid using relative position.
|
|
Remember to update before calling this.
|
|
|
|
Args:
|
|
vector(tuple): int
|
|
Returns:
|
|
bool: if camera moved.
|
|
"""
|
|
logger.info('Map swipe: %s' % str(vector))
|
|
vector = np.array(vector)
|
|
self.camera = tuple(vector + self.camera)
|
|
vector = np.array([0.5, 0.5]) - np.array(self.grids.center_offset) + vector
|
|
try:
|
|
self._map_swipe(vector)
|
|
except PerspectiveError as e:
|
|
self.handle_camera_outside_map(e)
|
|
|
|
def focus_to_grid_center(self):
|
|
"""
|
|
Re-focus to the center of a grid.
|
|
|
|
Returns:
|
|
bool: Map swiped.
|
|
"""
|
|
if np.any(np.abs(self.grids.center_offset - 0.5) > self.config.MAP_GRID_CENTER_TOLERANCE):
|
|
logger.info('Re-focus to grid center.')
|
|
self.map_swipe((0, 0))
|
|
return True
|
|
|
|
return False
|
|
|
|
def update(self, camera=True):
|
|
"""Update map image
|
|
|
|
Args:
|
|
camera: True to update camera position and perspective data.
|
|
"""
|
|
self.device.screenshot()
|
|
if not camera:
|
|
self.grids.update(image=self.device.image)
|
|
return True
|
|
|
|
try:
|
|
self.grids = Grids(self.device.image, config=self.config)
|
|
except Exception as e:
|
|
if self.info_bar_count():
|
|
logger.info('Perspective error cause by info bar. Waiting.')
|
|
self.handle_info_bar()
|
|
return self.update(camera=camera)
|
|
else:
|
|
raise e
|
|
|
|
# Catch perspective error
|
|
known_exception = self.info_bar_count()
|
|
if len(self.grids.horizontal) > self.map.shape[1] + 2 or len(self.grids.vertical) > self.map.shape[0] + 2:
|
|
if not known_exception:
|
|
logger.info('Perspective Error. Too many lines')
|
|
self.grids.correct = False
|
|
if len(self.grids.horizontal) <= 3 or len(self.grids.vertical) <= 3:
|
|
if not known_exception:
|
|
logger.info('Perspective Error. Too few lines')
|
|
self.grids.correct = False
|
|
|
|
if not self.grids.correct:
|
|
if self.info_bar_count():
|
|
logger.info('Perspective error cause by info bar. Waiting.')
|
|
self.handle_info_bar()
|
|
return self.update(camera=camera)
|
|
else:
|
|
self.grids.save_error_image()
|
|
|
|
# Set camera position
|
|
if self.grids.left_edge:
|
|
x = 0 + self.grids.center_grid[0]
|
|
elif self.grids.right_edge:
|
|
x = self.map.shape[0] - self.grids.shape[0] + self.grids.center_grid[0]
|
|
else:
|
|
x = self.camera[0]
|
|
if self.grids.lower_edge:
|
|
y = 0 + self.grids.center_grid[1]
|
|
elif self.grids.upper_edge:
|
|
y = self.map.shape[1] - self.grids.shape[1] + self.grids.center_grid[1]
|
|
else:
|
|
y = self.camera[1]
|
|
|
|
if self.camera != (x, y):
|
|
logger.info(f' camera corrected: {location2node(self.camera)} -> {location2node((x, y))}')
|
|
self.camera = (x, y)
|
|
self.show_camera()
|
|
|
|
def predict(self, is_carrier_scan=False):
|
|
self.grids.predict()
|
|
self.map.update(grids=self.grids, camera=self.camera, is_carrier_scan=is_carrier_scan)
|
|
|
|
def show_camera(self):
|
|
logger.info(' Camera: %s' % location2node(self.camera))
|
|
|
|
def ensure_edge_insight(self, reverse=False, preset=None):
|
|
"""
|
|
Swipe to bottom left until two edges insight.
|
|
Edges are used to locate camera.
|
|
|
|
Args:
|
|
reverse (bool): Reverse swipes.
|
|
preset (tuple(int)): Set in map swipe manually.
|
|
|
|
Returns:
|
|
list[tuple]: Swipe record.
|
|
"""
|
|
logger.info('Ensure edge in sight.')
|
|
record = []
|
|
|
|
while 1:
|
|
try:
|
|
if len(record) == 0:
|
|
self.update()
|
|
if preset is not None:
|
|
self.map_swipe(preset)
|
|
record.append(preset)
|
|
|
|
x = 0 if self.grids.left_edge or self.grids.right_edge else 3
|
|
y = 0 if self.grids.lower_edge or self.grids.upper_edge else 2
|
|
|
|
if len(record) > 0:
|
|
# Swipe even if two edges insight, this will avoid some embarrassing camera position.
|
|
self.map_swipe((x, y))
|
|
|
|
except PerspectiveError as e:
|
|
self.handle_camera_outside_map(e)
|
|
continue
|
|
|
|
record.append((x, y))
|
|
|
|
if x == 0 and y == 0:
|
|
break
|
|
|
|
if reverse:
|
|
logger.info('Reverse swipes.')
|
|
for vector in record[::-1]:
|
|
x, y = vector
|
|
if x != 0 and y != 0:
|
|
self.map_swipe((-x, -y))
|
|
|
|
return record
|
|
|
|
def handle_camera_outside_map(self, e):
|
|
msg = str(e).split(':')[1].strip()
|
|
logger.info(f'Camera outside map: {msg}')
|
|
dic = {'to the left': (2, 0), 'to the right': (-2, 0), 'to the lower': (0, 2), 'to the upper': (0, -2)}
|
|
self._map_swipe(dic[msg])
|
|
|
|
def focus_to(self, location, swipe_limit=(3, 2)):
|
|
"""Focus camera on a grid
|
|
|
|
Args:
|
|
location: grid
|
|
swipe_limit(tuple): (x, y). Limit swipe in (-x, -y, x, y).
|
|
"""
|
|
location = location_ensure(location)
|
|
logger.info('Focus to: %s' % location2node(location))
|
|
|
|
vector = np.array(location) - self.camera
|
|
vector, sign = np.abs(vector), np.sign(vector)
|
|
while 1:
|
|
|
|
swipe = (
|
|
vector[0] if vector[0] < swipe_limit[0] else swipe_limit[0],
|
|
vector[1] if vector[1] < swipe_limit[1] else swipe_limit[1]
|
|
)
|
|
self.map_swipe(tuple(sign * swipe))
|
|
|
|
vector -= swipe
|
|
if np.all(np.abs(vector) <= 0):
|
|
break
|
|
|
|
def full_scan(self, battle_count=None, mystery_count=0, siren_count=0, carrier_count=0, is_carrier_scan=False):
|
|
"""Scan the hole map.
|
|
|
|
Args:
|
|
battle_count:
|
|
mystery_count:
|
|
siren_count:
|
|
carrier_count:
|
|
is_carrier_scan:
|
|
"""
|
|
logger.info('Full scan start')
|
|
self.map.reset_fleet()
|
|
|
|
queue = self.map.camera_data
|
|
if battle_count == 0:
|
|
queue.add(self.map.camera_data_spawn_point)
|
|
|
|
while len(queue) > 0:
|
|
if self.map.missing_is_none(battle_count, mystery_count, siren_count, carrier_count):
|
|
if battle_count == 0 and queue.count != queue.delete(self.map.camera_data_spawn_point).count:
|
|
logger.info('Continue scanning spawn points.')
|
|
pass
|
|
else:
|
|
logger.info('All spawn found, Early stopped.')
|
|
break
|
|
queue = queue.sort_by_camera_distance(self.camera)
|
|
self.focus_to(queue[0])
|
|
self.predict(is_carrier_scan=is_carrier_scan)
|
|
queue = queue[1:]
|
|
|
|
if battle_count is not None:
|
|
self.map.missing_predict(battle_count=battle_count, mystery_count=mystery_count, siren_count=siren_count,
|
|
carrier_count=carrier_count)
|
|
self.map.show()
|
|
|
|
def in_sight(self, location, sight=None):
|
|
"""Make sure location in camera sight
|
|
|
|
Args:
|
|
location:
|
|
sight (tuple): Such as (-3, -1, 3, 2).
|
|
"""
|
|
location = location_ensure(location)
|
|
logger.info('In sight: %s' % location2node(location))
|
|
if sight is None:
|
|
sight = self.map.camera_sight
|
|
|
|
diff = np.array(location) - self.camera
|
|
if diff[1] > sight[3]:
|
|
y = diff[1] - sight[3]
|
|
elif diff[1] < sight[1]:
|
|
y = diff[1] - sight[1]
|
|
else:
|
|
y = 0
|
|
if diff[0] > sight[2]:
|
|
x = diff[0] - sight[2]
|
|
elif diff[0] < sight[0]:
|
|
x = diff[0] - sight[0]
|
|
else:
|
|
x = 0
|
|
self.focus_to((self.camera[0] + x, self.camera[1] + y))
|
|
|
|
def convert_map_to_grid(self, location):
|
|
"""If self.grids doesn't contain this location, focus camera on the location and re-convert it.
|
|
|
|
Args:
|
|
location: Grid instance in self.map.
|
|
|
|
Returns:
|
|
Grid: Grid instance in self.grids.
|
|
"""
|
|
location = location_ensure(location)
|
|
|
|
grid = np.array(location) - self.camera + self.grids.center_grid
|
|
logger.info('Convert_map_to_grid')
|
|
logger.info(f'Map: {location2node(location)}, '
|
|
f'Camera: {location2node(self.camera)}, '
|
|
f'Center: {location2node(self.grids.center_grid)}, '
|
|
f'grid: {location2node(grid)}')
|
|
if grid in self.grids:
|
|
return self.grids[grid]
|
|
else:
|
|
logger.warning('Convert_map_to_grid Failed.')
|
|
self.grids.save_error_image()
|
|
self.focus_to(location)
|
|
grid = np.array(location) - self.camera + self.grids.center_grid
|
|
return self.grids[grid]
|
|
|
|
def full_scan_find_boss(self):
|
|
logger.info('Full scan find boss.')
|
|
self.map.reset_fleet()
|
|
|
|
queue = self.map.select(may_boss=True)
|
|
while len(queue) > 0:
|
|
queue = queue.sort_by_camera_distance(self.camera)
|
|
self.in_sight(queue[0])
|
|
self.predict()
|
|
queue = queue[1:]
|
|
|
|
boss = self.map.select(is_boss=True)
|
|
boss = boss.add(self.map.select(may_boss=True, is_enemy=True))
|
|
if boss:
|
|
logger.info(f'Boss found: {boss}')
|
|
self.map.show()
|
|
return True
|
|
|
|
logger.warning('No boss found.')
|
|
return False
|