mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-30 03:16:08 +00:00
e8b3e7916c
- Predict fleets when having 2 fleet spawn_point but only 1 fleet detected
445 lines
15 KiB
Python
445 lines
15 KiB
Python
from module.logger import logger
|
|
from module.map.fleet import Fleet
|
|
from module.map.grid_info import GridInfo
|
|
from module.map.map_grids import SelectedGrids, RoadGrids
|
|
|
|
|
|
class Map(Fleet):
|
|
def clear_chosen_enemy(self, grid, expected=''):
|
|
"""
|
|
Args:
|
|
grid (GridInfo):
|
|
expected (str):
|
|
"""
|
|
logger.info('Clear enemy: %s' % grid)
|
|
expected = f'combat_{expected}' if expected else 'combat'
|
|
self.show_fleet()
|
|
if self.config.ENABLE_EMOTION_REDUCE and self.config.ENABLE_MAP_FLEET_LOCK:
|
|
self.emotion.wait(fleet=self.fleet_current_index)
|
|
self.goto(grid, expected=expected)
|
|
|
|
self.full_scan()
|
|
self.find_path_initial()
|
|
self.map.show_cost()
|
|
|
|
def clear_chosen_mystery(self, grid):
|
|
"""
|
|
Args:
|
|
grid (GridInfo):
|
|
"""
|
|
logger.info('Clear mystery: %s' % grid)
|
|
self.show_fleet()
|
|
self.goto(grid, expected='mystery')
|
|
# self.mystery_count += 1
|
|
self.map.show_cost()
|
|
|
|
def pick_up_ammo(self, grid=None):
|
|
"""
|
|
Args:
|
|
grid (GridInfo):
|
|
"""
|
|
if grid is None:
|
|
grid = self.map.select(may_ammo=True)
|
|
if not grid:
|
|
logger.info('Map has no ammo.')
|
|
return False
|
|
grid = grid[0]
|
|
|
|
if self.ammo_count > 0 and grid.is_accessible:
|
|
logger.info('Pick up ammo: %s' % grid)
|
|
self.goto(grid, expected='')
|
|
self.ensure_no_info_bar()
|
|
|
|
# self.ammo_count -= 5 - self.battle_count
|
|
recover = 5 - self.fleet_ammo
|
|
recover = 3 if recover > 3 else recover
|
|
logger.attr('Got ammo', recover)
|
|
|
|
self.ammo_count -= recover
|
|
self.fleet_ammo += recover
|
|
|
|
@staticmethod
|
|
def select_grids(grids, nearby=False, is_accessible=True, scale=(), genre=(), strongest=False, weakest=False,
|
|
cost=True, weight=True, ignore=None):
|
|
"""
|
|
Args:
|
|
grids (SelectedGrids):
|
|
nearby (bool):
|
|
is_accessible (bool):
|
|
scale (tuple[int], list[int]): Tuple: select out of order, list: select in order.
|
|
genre (tuple[str], list[str]): light, main, carrier, treasure. (Case insensitive).
|
|
strongest (bool):
|
|
weakest (bool):
|
|
cost (bool):
|
|
weight (bool):
|
|
ignore (SelectedGrids):
|
|
|
|
Returns:
|
|
SelectedGrids:
|
|
"""
|
|
if nearby:
|
|
grids = grids.select(is_nearby=True)
|
|
if is_accessible:
|
|
grids = grids.select(is_accessible=True)
|
|
if ignore is not None:
|
|
grids = grids.delete(grids=ignore)
|
|
if len(scale):
|
|
enemy = SelectedGrids([])
|
|
for enemy_scale in scale:
|
|
enemy = enemy.add(grids.select(enemy_scale=enemy_scale))
|
|
if isinstance(scale, list) and enemy:
|
|
break
|
|
grids = enemy
|
|
if len(genre):
|
|
enemy = SelectedGrids([])
|
|
for enemy_genre in genre:
|
|
enemy = enemy.add(grids.select(enemy_genre=enemy_genre.capitalize()))
|
|
if isinstance(genre, list) and enemy:
|
|
break
|
|
grids = enemy
|
|
if strongest:
|
|
for scale in [3, 2, 1, 0]:
|
|
enemy = grids.select(enemy_scale=scale)
|
|
if enemy:
|
|
grids = enemy
|
|
break
|
|
if weakest:
|
|
for scale in [1, 2, 3, 0]:
|
|
enemy = grids.select(enemy_scale=scale)
|
|
if enemy:
|
|
grids = enemy
|
|
break
|
|
|
|
if grids:
|
|
grids = grids.sort(cost=cost, weight=weight)
|
|
|
|
return grids
|
|
|
|
@staticmethod
|
|
def show_select_grids(grids, **kwargs):
|
|
length = 3
|
|
keys = list(kwargs.keys())
|
|
for index in range(0, len(keys), length):
|
|
text = [f'{key}={kwargs[key]}' for key in keys[index:index + length]]
|
|
text = ', '.join(text)
|
|
logger.info(text)
|
|
|
|
logger.info(f'Grids: {grids}')
|
|
|
|
def clear_all_mystery(self, **kwargs):
|
|
"""Methods to pick up all mystery.
|
|
|
|
Returns:
|
|
bool: False, because didn't clear any enemy.
|
|
"""
|
|
while 1:
|
|
grids = self.map.select(is_mystery=True)
|
|
grids = self.select_grids(grids, **kwargs)
|
|
|
|
if not grids:
|
|
break
|
|
|
|
logger.hr('Clear all mystery')
|
|
self.show_select_grids(grids, **kwargs)
|
|
self.clear_chosen_mystery(grids[0])
|
|
|
|
return False
|
|
|
|
def clear_enemy(self, **kwargs):
|
|
"""Methods to clear a enemy. May not do anything if no suitable enemy.
|
|
|
|
Returns:
|
|
bool: True if clear an enemy.
|
|
"""
|
|
grids = self.map.select(is_enemy=True, is_boss=False)
|
|
grids = self.select_grids(grids, **kwargs)
|
|
|
|
if grids:
|
|
logger.hr('Clear enemy')
|
|
self.show_select_grids(grids, **kwargs)
|
|
self.clear_chosen_enemy(grids[0])
|
|
return True
|
|
|
|
return False
|
|
|
|
def clear_roadblocks(self, roads, **kwargs):
|
|
"""Clear roadblocks.
|
|
|
|
Args:
|
|
roads(list[RoadGrids]):
|
|
|
|
Returns:
|
|
bool: True if clear an enemy.
|
|
"""
|
|
grids = SelectedGrids([])
|
|
for road in roads:
|
|
grids = grids.add(road.roadblocks())
|
|
|
|
grids = self.select_grids(grids, **kwargs)
|
|
|
|
if grids:
|
|
logger.hr('Clear roadblock')
|
|
self.show_select_grids(grids, **kwargs)
|
|
self.clear_chosen_enemy(grids[0])
|
|
return True
|
|
|
|
return False
|
|
|
|
def clear_potential_roadblocks(self, roads, **kwargs):
|
|
"""Avoid roadblock that only has one grid empty.
|
|
|
|
Args:
|
|
roads(list[RoadGrids]):
|
|
|
|
Returns:
|
|
bool: True if clear an enemy.
|
|
"""
|
|
grids = SelectedGrids([])
|
|
for road in roads:
|
|
grids = grids.add(road.potential_roadblocks())
|
|
|
|
grids = self.select_grids(grids, **kwargs)
|
|
|
|
if grids:
|
|
logger.hr('Avoid potential roadblock')
|
|
self.show_select_grids(grids, **kwargs)
|
|
self.clear_chosen_enemy(grids[0])
|
|
return True
|
|
|
|
return False
|
|
|
|
def clear_grids_for_faster(self, grids, **kwargs):
|
|
"""Clear some grids to walk a shorter distance.
|
|
|
|
Args:
|
|
grids(SelectedGrids):
|
|
|
|
Returns:
|
|
bool: True if clear an enemy.
|
|
"""
|
|
|
|
grids = grids.select(is_enemy=True)
|
|
grids = self.select_grids(grids, **kwargs)
|
|
|
|
if grids:
|
|
logger.hr('Clear grids for faster')
|
|
self.show_select_grids(grids, **kwargs)
|
|
self.clear_chosen_enemy(grids[0])
|
|
return True
|
|
|
|
return False
|
|
|
|
def clear_boss(self):
|
|
"""This method is deprecated, although it works well in simple map.
|
|
In a complex map, brute_clear_boss is recommended.
|
|
|
|
Returns:
|
|
bool:
|
|
"""
|
|
grids = self.map.select(is_boss=True, is_accessible=True)
|
|
logger.info('Is boss: %s' % self.map.select(is_boss=True))
|
|
if not grids.count:
|
|
grids = grids.add(self.map.select(may_boss=True, is_enemy=True, is_accessible=True))
|
|
logger.warning('Boss not detected, using may_boss grids.')
|
|
logger.info('May boss: %s' % self.map.select(may_boss=True))
|
|
logger.info('May boss and is enemy: %s' % self.map.select(may_boss=True, is_enemy=True))
|
|
|
|
if grids:
|
|
logger.hr('Clear BOSS')
|
|
grids = grids.sort(cost=True, weight=True)
|
|
logger.info('Grids: %s' % str(grids))
|
|
self.clear_chosen_enemy(grids[0])
|
|
|
|
logger.warning('BOSS not detected, trying all boss spawn point.')
|
|
return self.clear_potential_boss()
|
|
|
|
def clear_potential_boss(self):
|
|
"""
|
|
Method to step on all boss spawn point when boss not detected.
|
|
"""
|
|
grids = self.map.select(may_boss=True, is_accessible=True)
|
|
logger.info('May boss: %s' % self.map.select(may_boss=True))
|
|
battle_count = self.battle_count
|
|
|
|
for grid in grids:
|
|
logger.hr('Clear potential BOSS')
|
|
grids = grids.sort(cost=True, weight=True)
|
|
logger.info('Grid: %s' % str(grid))
|
|
self.clear_chosen_enemy(grid)
|
|
if self.battle_count > battle_count:
|
|
logger.info('Boss guessing correct.')
|
|
return True
|
|
else:
|
|
logger.info('Boss guessing incorrect.')
|
|
|
|
return False
|
|
|
|
def brute_clear_boss(self):
|
|
"""
|
|
Method to clear boss, using brute-force to find roadblocks.
|
|
"""
|
|
boss = self.map.select(is_boss=True)
|
|
if boss:
|
|
logger.info('Brute clear BOSS')
|
|
fleet = 2 if self.config.FLEET_BOSS == 2 and self.config.FLEET_2 else 1
|
|
grids = self.brute_find_roadblocks(boss[0], fleet=fleet)
|
|
if grids:
|
|
if self.brute_fleet_meet():
|
|
return True
|
|
logger.info('Brute clear BOSS roadblocks')
|
|
grids = grids.sort(cost=True, weight=True)
|
|
logger.info('Grids: %s' % str(grids))
|
|
self.clear_chosen_enemy(grids[0])
|
|
return True
|
|
else:
|
|
return self.fleet_boss.clear_boss()
|
|
elif self.map.select(may_boss=True, is_caught_by_siren=True):
|
|
logger.info('BOSS appear on fleet grid')
|
|
self.fleet_2.switch_to()
|
|
self.clear_chosen_enemy(self.map.select(may_boss=True, is_caught_by_siren=True)[0])
|
|
else:
|
|
logger.warning('BOSS not detected, trying all boss spawn point.')
|
|
return self.clear_potential_boss()
|
|
|
|
def brute_fleet_meet(self):
|
|
"""
|
|
Method to clear roadblocks between fleets, using brute-force to find roadblocks.
|
|
"""
|
|
if not self.config.FLEET_2 or not self.fleet_2_location:
|
|
return False
|
|
grids = self.brute_find_roadblocks(self.map[self.fleet_2_location], fleet=1)
|
|
if grids:
|
|
logger.info('Brute clear roadblocks between fleets.')
|
|
grids = grids.sort(cost=True, weight=True)
|
|
logger.info('Grids: %s' % str(grids))
|
|
self.clear_chosen_enemy(grids[0])
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def clear_siren(self, **kwargs):
|
|
"""
|
|
Returns:
|
|
bool: True if clear an enemy.
|
|
"""
|
|
if not self.config.MAP_HAS_SIREN:
|
|
return False
|
|
|
|
logger.info('May siren: %s' % self.map.select(may_siren=True))
|
|
logger.info('May siren and is enemy: %s' % self.map.select(may_siren=True, is_enemy=True))
|
|
grids = self.map.select(may_siren=True, is_enemy=True)
|
|
|
|
logger.info('Is siren: %s' % self.map.select(is_siren=True))
|
|
grids = grids.add(self.map.select(is_siren=True))
|
|
|
|
if self.config.POOR_MAP_DATA or not self.is_map_green:
|
|
logger.info('Is 0 scale enemy: %s' % self.map.select(is_enemy=True, enemy_scale=0))
|
|
grids = grids.add(self.map.select(is_enemy=True, enemy_scale=0))
|
|
|
|
logger.info('Delete is boss: %s' % self.map.select(is_boss=True))
|
|
grids = grids.delete(self.map.select(is_boss=True))
|
|
|
|
grids = self.select_grids(grids, **kwargs)
|
|
|
|
if grids:
|
|
logger.hr('Clear siren')
|
|
self.show_select_grids(grids, **kwargs)
|
|
self.clear_chosen_enemy(grids[0], expected='siren')
|
|
return True
|
|
|
|
return False
|
|
|
|
def fleet_2_step_on(self, grids, roadblocks):
|
|
"""Fleet step on a grid which can reduce the ambush frequency another fleet.
|
|
Of course, you can simply use 'self.fleet_2.goto(grid)' and do the same thing.
|
|
However, roads can be block by enemy and this method can handle that.
|
|
|
|
Args:
|
|
grids (SelectedGrids):
|
|
roadblocks (list[RoadGrids]):
|
|
|
|
Returns:
|
|
bool: if clear an enemy.
|
|
"""
|
|
if not self.config.FLEET_2:
|
|
return False
|
|
for grid in grids:
|
|
if self.fleet_at(grid=grid, fleet=2):
|
|
return False
|
|
|
|
logger.info('Fleet 2 step on')
|
|
for grid in grids:
|
|
if grid.is_enemy or grid.is_cleared:
|
|
continue
|
|
if self.check_accessibility(grid=grid, fleet=2):
|
|
logger.info('Fleet_2 step on %s' % grid)
|
|
self.fleet_2.goto(grid)
|
|
self.fleet_1.switch_to()
|
|
return False
|
|
|
|
logger.info('Fleet_2 step on got roadblocks.')
|
|
self.fleet_1.clear_roadblocks(roadblocks)
|
|
self.fleet_1.clear_all_mystery()
|
|
return True
|
|
|
|
def fleet_2_break_siren_caught(self):
|
|
if not self.config.FLEET_2:
|
|
return False
|
|
if not self.config.MAP_HAS_SIREN or not self.config.MAP_HAS_MOVABLE_ENEMY:
|
|
return False
|
|
if not self.map.select(is_caught_by_siren=True):
|
|
logger.info('No fleet caught by siren.')
|
|
return False
|
|
if not self.fleet_2_location or not self.map[self.fleet_2_location].is_caught_by_siren:
|
|
logger.warning('Appear caught by siren, but not fleet_2.')
|
|
for grid in self.map:
|
|
grid.is_caught_by_siren = False
|
|
return False
|
|
|
|
logger.info(f'Break siren caught, fleet_2: {self.fleet_2_location}')
|
|
self.fleet_2.switch_to()
|
|
self.ensure_edge_insight()
|
|
self.clear_chosen_enemy(self.map[self.fleet_2_location])
|
|
self.fleet_1.switch_to()
|
|
for grid in self.map:
|
|
grid.is_caught_by_siren = False
|
|
return True
|
|
|
|
def fleet_2_push_forward(self):
|
|
"""Move fleet 2 to the grid with lower grid.weight
|
|
This will reduce the possibility of Boss fleet get stuck by enemies, especially for those one-way-road map
|
|
from chapter 7 to chapter 9.
|
|
|
|
Know more (in Chinese simplified):
|
|
9章道中战最小化路线规划 (Route Planning for battle minimization in chapter 9)
|
|
https://wiki.biligame.com/blhx/9%E7%AB%A0%E9%81%93%E4%B8%AD%E6%88%98%E6%9C%80%E5%B0%8F%E5%8C%96%E8%B7%AF%E7%BA%BF%E8%A7%84%E5%88%92
|
|
|
|
Returns:
|
|
bool: If pushed forward.
|
|
"""
|
|
if not self.config.FLEET_2:
|
|
return False
|
|
|
|
logger.info('Fleet_2 push forward')
|
|
grids = self.map.select(is_land=False).sort(cost=True, weight=True)
|
|
if self.map[self.fleet_2_location].weight <= grids[0].weight:
|
|
logger.info('Fleet_2 pushed to destination')
|
|
return False
|
|
|
|
self.find_path_initial(self.fleet_2_location)
|
|
fleets = SelectedGrids([self.map[self.fleet_1_location], self.map[self.fleet_2_location]])
|
|
grids = grids.select(is_accessible=True, is_sea=True).delete(fleets)
|
|
self.find_path_initial()
|
|
if not grids:
|
|
logger.info('Fleet_2 has no where to push')
|
|
return False
|
|
if self.map[self.fleet_2_location].weight <= grids[0].weight:
|
|
logger.info('Fleet_2 pushed to closest grid')
|
|
return False
|
|
|
|
logger.info(f'Grids: {grids}')
|
|
logger.info(f'Push forward: {grids[0]}')
|
|
self.fleet_2.goto(grids[0])
|
|
self.fleet_1.switch_to()
|
|
return True
|