async def _control_inject_queen_near_base( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, queen: Unit, townhall: Unit, grid: Optional[np.ndarray] = None, ) -> None: """ Between injects, we want the Queen to have the following behavior: - Attack any enemy that gets too close - Move the Queen back if she goes too far from the townhall - Stay out of the mineral line, incase bot has custom mineral gathering (don't block workers) """ # don't do anything else, just move the queen back if queen.distance_to(townhall) > 8: queen.move(townhall) return close_threats: Units = Units([], self.bot) # we can only have close threats if enemy are near our bases in the first place # so save calculation otherwise if air_threats_near_bases or ground_threats_near_bases: close_threats = self.bot.enemy_units.filter( lambda enemy: enemy.position.distance_to(townhall) < 12) if close_threats: await self.do_queen_micro(queen, close_threats, grid) # every now and then, check queen is not in the mineral field blocking workers elif self.bot.state.game_loop % 32 == 0: close_mfs: Units = self.bot.mineral_field.filter( lambda mf: mf.distance_to(townhall) < 8) # make a small adjustment away from the minerals if close_mfs and queen.distance_to(close_mfs.center) < 6: queen.move(queen.position.towards(close_mfs.center, -1))
def move_phoenix(self, phoenix: Unit): enemy_main = self.knowledge.enemy_expansion_zones[0].center_location if phoenix.distance_to(enemy_main) <= 10: self.reached_enemy_main = True if not self.reached_enemy_main: target = self.select_target() self.do(phoenix.move(target)) if target != self.last_target: self.last_target = target self.print(f"scouting {target}, interval {self.time_interval}") else: # This else makes it scout around possible main tech positions if self.last_target is None: self.last_target = self.select_target() if self.ai.enemy_race == Race.Protoss: new_target = self.knowledge.enemy_expansion_zones[ 0].behind_mineral_position_center.towards( self.knowledge.enemy_expansion_zones[0]. mineral_line_center, 20).random_on_distance(7) if phoenix.distance_to(self.last_target) <= 3: self.do(phoenix.move(new_target)) self.last_target = new_target elif self.ai.enemy_race == Race.Terran: new_target = self.knowledge.enemy_expansion_zones[ 1].behind_mineral_position_center.towards( self.knowledge.enemy_expansion_zones[1]. mineral_line_center, 20).random_on_distance(7) if phoenix.distance_to(self.last_target) <= 3: self.do(phoenix.move(new_target)) self.last_target = new_target
def use_force_field(self, enemies: EnemyData, sentry: Unit, time: float): force_field_is_good_idea = self.last_force_field_time + self.force_field_cooldown < time for key, value in self.tag_shield_used_dict.items(): if value + 5 < time: # Some sentry has used its shield, it might be a good idea to use FF now force_field_is_good_idea = True if not force_field_is_good_idea: return [] # Hard priorizationg for guardian shield. relevant_enemies = enemies.close_enemies.not_structure.not_flying\ .exclude_type(UnitValue.worker_types).exclude_type(UnitTypeId.SIEGETANKSIEGED) if sentry.energy < FORCE_FIELD_ENERGY_COST or relevant_enemies.amount < 5 or enemies.enemy_power.ground_presence < 15: return [] center = relevant_enemies.center if self.last_force_field_time + 2 > time and len(self.last_force_field_positions) > self.last_force_fields_used: index = self.last_force_fields_used position = self.last_force_field_positions[index] if sentry.distance_to(position) < 9: self.last_force_fields_used += 1 self.knowledge.print(f"JOINING FORCE FIELDS {position}!") return [CombatAction(sentry, position, False, ability= AbilityId.FORCEFIELD_FORCEFIELD)] else: self.knowledge.print(f"TOO FAR AWAY TO JOIN FORCE FIELDS {position}!") return[CombatAction(sentry, position, False)] if self.last_force_field_time + self.force_field_cooldown < time: point: Point2 = relevant_enemies.closest_to(sentry).position target_center = point.towards(center, 3) distance = sentry.distance_to(target_center) if distance < 9: # Activate force fields! self.last_force_field_time = time self.last_force_fields_used = 1 point = sentry.position point = target_center.towards(point, 1) direction_v = target_center.offset( (-point.x, -point.y)) perpenticular_v = Point2((direction_v.y, -direction_v.x)) if enemies.powered_enemies(UnitTypeId.ZERGLING).amount - 10 > enemies.powered_enemies.amount: if enemies.powered_enemies.amount > 10: self.last_force_field_positions = self.ff_pos(target_center, direction_v, perpenticular_v, ff_centercube) else: self.last_force_field_positions = self.ff_pos(target_center, direction_v, perpenticular_v, ff_triangle) elif enemies.enemy_power.ground_presence > 25: # Only use 5x FF against huge masses self.last_force_field_positions = self.ff_pos(target_center, direction_v, perpenticular_v, ff_wall5) else: self.last_force_field_positions = self.ff_pos(target_center, direction_v, perpenticular_v, ff_wall3) self.knowledge.print(f"Force fields ordered to: {self.last_force_field_positions}") return [CombatAction(sentry, self.last_force_field_positions[0], False, ability=AbilityId.FORCEFIELD_FORCEFIELD)] return []
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, unit: Unit, th_tag: int = 0, ) -> None: if self.policy.pass_own_threats: air_threats: Units = air_threats_near_bases ground_threats: Units = ground_threats_near_bases else: air_threats: Units = self.enemy_air_threats ground_threats: Units = self.enemy_ground_threats transfuse_target: Unit = self.get_transfuse_target(unit.position) self.used_transfuse_this_step: bool = False if (transfuse_target and transfuse_target is not unit and not self.used_transfuse_this_step): unit(AbilityId.TRANSFUSION_TRANSFUSION, transfuse_target) self.used_transfuse_this_step = True elif self.priority_enemy_units: await self.do_queen_micro(unit, self.priority_enemy_units) elif self.policy.attack_condition(): await self.do_queen_offensive_micro(unit, self.policy.attack_target) elif self.policy.defend_against_ground and ground_threats: await self.do_queen_micro(unit, ground_threats) elif self.policy.defend_against_air and air_threats: await self.do_queen_micro(unit, air_threats) elif unit.distance_to(self.policy.rally_point) > 12: unit.move(self.policy.rally_point)
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, priority_enemy_units: Units, unit: Unit, th_tag: int = 0, grid: Optional[np.ndarray] = None, nydus_networks: Optional[Units] = None, nydus_canals: Optional[Units] = None, ) -> None: canal: Optional[Unit] = None network: Optional[Unit] = None # canal is what we place else where on the map if nydus_canals: canal = nydus_canals.closest_to(self.policy.nydus_target) # network is what is morphed from a drone if nydus_networks: network = nydus_networks.closest_to(self.bot.start_location) unit_distance_to_target: float = unit.distance_to(self.policy.nydus_target) if canal and network: await self._manage_nydus_attack( canal, network, unit, unit_distance_to_target, grid )
def position_terran(self, unit: Unit) -> Optional[Point2]: """ Copied and modified from grid_building.py Finds the closest landing location to dettach from addons. """ buildings = self.ai.structures current_location: Optional[Point2] = None current_distance = math.inf reserved_landing_locations: Set[Point2] = set( self.knowledge.building_solver.structure_target_move_location. values()) for point in self.knowledge.building_solver.building_position: # If a structure is landing here from AddonSwap() then dont use this location if point in reserved_landing_locations: continue # If this location has a techlab or reactor next to it, then don't create a new structure here if point in self.knowledge.building_solver.free_addon_locations: continue if buildings.closer_than(1, point): continue dist = unit.distance_to(point) if dist < current_distance: current_location = point current_distance = dist return current_location
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, unit: Unit, priority_enemy_units: Units, th_tag: int = 0, ) -> None: should_spread_creep: bool = self._check_queen_can_spread_creep(unit) self.creep_targets = self.policy.creep_targets if priority_enemy_units: await self.do_queen_micro(unit, priority_enemy_units) elif (self.policy.defend_against_air and air_threats_near_bases and not should_spread_creep): await self.do_queen_micro(unit, air_threats_near_bases) elif (self.policy.defend_against_ground and ground_threats_near_bases and not should_spread_creep): await self.do_queen_micro(unit, ground_threats_near_bases) elif self.bot.enemy_units and self.bot.enemy_units.filter( # custom filter to replace in_attack_range_of so that it can be used with memory units lambda enemy: enemy.position.distance_to(unit) < max( unit.air_range, unit.ground_range)): unit.move(self.policy.rally_point) elif (unit.energy >= 25 and len(unit.orders) == 0 and self.creep_coverage < self.policy.target_perc_coverage): await self.spread_creep(unit) elif unit.distance_to(self.policy.rally_point) > 7: if len(unit.orders) > 0: if unit.orders[0].ability.button_name != "CreepTumor": unit.move(self.policy.rally_point) elif len(unit.orders) == 0: unit.move(self.policy.rally_point)
def _check_nydus_role(self, queen: Unit) -> None: """ If there are nydus's we may want to steal this queen for the nydus Or if this queen already has a nydus role, we should remove the nydus role if required """ steal_from: Set[UnitID] = self.nydus.policy.steal_from # check if queens should be assigned to nydus role, is there a network and a canal? if (self.nydus_networks and self.nydus_canals and self.nydus.policy.active and len(self.nydus_queen_tags) < self.nydus.policy.max_queens and queen.tag not in self.nydus_queen_tags and queen.tag in self.assigned_queen_tags): # queen can only be in one role role_to_check: QueenRoles = ( QueenRoles.Defence if queen.tag in self.defence_queen_tags else (QueenRoles.Creep if queen.tag in self.creep_queen_tags else QueenRoles.Defence)) # queen role is in one of the allowed roles to steal from if role_to_check in steal_from: self.remove_unit(queen.tag) self.assigned_queen_tags.add(queen.tag) self.nydus_queen_tags.append(queen.tag) # TODO: Work out how to handle aborting a Nydus: # - Policy option for when Queen goes back into canal if too much danger? # - What if the canal dies and the queen has an escape path? # At the moment assigning a Queen to Nydus is a one way trip # Here we only handle, Queens being assigned to Nydus and then the canal getting destroyed in the meantime if (queen.tag in self.nydus_queen_tags and queen.distance_to(self.nydus.policy.nydus_target) > 50 and not self.nydus_canals): # removing should be enough, queen then should be given a new role automatically self.remove_unit(queen.tag)
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, unit: Unit, th_tag: int, ) -> None: if self.policy.pass_own_threats: air_threats: Units = air_threats_near_bases ground_threats: Units = ground_threats_near_bases else: air_threats: Units = self.enemy_air_threats ground_threats: Units = self.enemy_ground_threats ths: Units = self.bot.townhalls.ready.tags_in([th_tag]) if ths: th: Unit = ths.first if self.priority_enemy_units: await self.do_queen_micro(unit, self.priority_enemy_units) elif self.policy.defend_against_air and air_threats: await self.do_queen_micro(unit, air_threats) elif self.policy.defend_against_ground and ground_threats: await self.do_queen_micro(unit, ground_threats) else: if unit.energy >= 25: unit(AbilityId.EFFECT_INJECTLARVA, th) # regardless of policy, chase away enemy close to th # but if queen gets too far away, walk back to th elif unit.distance_to(th) > 7: unit.move(th.position) elif self.bot.enemy_units.filter( lambda enemy: enemy.position.distance_to(unit) < 10): unit.attack( self.find_closest_enemy(unit, self.bot.enemy_units))
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, priority_enemy_units: Units, unit: Unit, th_tag: int = 0, grid: Optional[np.ndarray] = None, nydus_networks: Optional[Units] = None, nydus_canals: Optional[Units] = None, ) -> None: if priority_enemy_units: await self.do_queen_micro(unit, priority_enemy_units, grid) elif self.policy.attack_condition(): await self.do_queen_offensive_micro(unit, self.policy.attack_target) elif self.policy.defend_against_ground and ground_threats_near_bases: await self.do_queen_micro(unit, ground_threats_near_bases, grid) elif self.policy.defend_against_air and air_threats_near_bases: await self.do_queen_micro(unit, air_threats_near_bases, grid) elif (self.map_data and grid is not None and not self.is_position_safe(grid, unit.position)): await self.move_towards_safe_spot(unit, grid) elif unit.distance_to(self.policy.rally_point) > 12: unit.move(self.policy.rally_point)
def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action: if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25: return current_command if self.move_type in {MoveType.PanicRetreat, MoveType.DefensiveRetreat}: return current_command if self.cd_manager.is_ready(unit.tag, AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT): close_enemies = self.cache.enemy_in_range(unit.position, 7).filter(lambda u: u.is_armored) if close_enemies: return Action(None, False, AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT) shoot = self.should_shoot(unit) if not shoot: if self.should_retreat(unit): pos = self.pather.find_weak_influence_air(unit.position, 4) return Action(pos, False) current_command = self.focus_fire(unit, current_command, None) if not shoot: if self.engaged_power.air_power < 1: if unit.distance_to(current_command.target) > 2: return Action(current_command.target.position, False) return current_command
def evasive_move_to(self, position_to, unit: Unit): enemy_anti_air_units = self.knowledge.unit_cache.enemy_in_range(unit.position3d, 11) \ .filter(lambda u: u.can_attack_air).visible if enemy_anti_air_units.exists: position = unit.position3d for aa in enemy_anti_air_units: distance = unit.distance_to(aa.position3d) amount_of_evade = 15 - distance if distance > 0: position = position.towards(aa, -amount_of_evade) # after the for loop, position is the best vector away from enemy distance_to_best_evade_point = unit.distance_to(position) should_go = position.towards(position_to, distance_to_best_evade_point) return Action(should_go, False) else: return Action(position_to, False)
async def harass_command(self, dt: Unit, prism: Unit): self.knowledge.roles.set_task(UnitTask.Reserved, dt) if self.is_revealed_by_enemy(dt): if dt.distance_to( prism) <= 5 and prism.shield_health_percentage >= 0.4: self.do(dt.smart(prism)) return True else: self.do(dt.move(prism)) return True else: await self.attack_priority_targets(dt, prism) return True
def is_revealed_by_enemy(self, dt: Unit) -> bool: detectors = self.knowledge.unit_cache.enemy_in_range(dt.position, 20) \ .filter(lambda x: (x.detect_range - 1) > dt.distance_to(x.position)) if detectors.exists: if detectors.filter(lambda x: x.is_flying).exists: self.enemy_air_detector = True return True for effect in self.ai.state.effects: if effect.id == EffectId.SCANNERSWEEP: if Point2.center(effect.positions).distance_to( dt.position) < 15: return True return False
def attack_prime(self, unit: Unit, target: Unit) -> bool: if ( self.WORKERS ): # probably should change this name as it's referring to closest enemy workers to the reaper workers = self.WORKERS.filter( lambda unit: unit.distance_to(unit.position) < 20) if len(workers) > 0: prime_target = min(self.WORKERS, key=lambda x: x.health_percentage) self.ai.do(unit.attack(prime_target)) return True self.ai.do(unit.attack(target)) return True
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, unit: Unit, th_tag: int = 0, ) -> None: if self.policy.pass_own_threats: air_threats: Units = air_threats_near_bases ground_threats: Units = ground_threats_near_bases else: air_threats: Units = self.enemy_air_threats ground_threats: Units = self.enemy_ground_threats should_spread_creep: bool = self._check_queen_can_spread_creep(unit) self.creep_targets = self.policy.creep_targets transfuse_target: Unit = self.get_transfuse_target(unit.position) # allow transfuse if energy has built up if unit.energy >= 50 and transfuse_target and transfuse_target is not unit: unit(AbilityId.TRANSFUSION_TRANSFUSION, transfuse_target) elif self.priority_enemy_units: await self.do_queen_micro(unit, self.priority_enemy_units) elif self.policy.defend_against_air and air_threats and not should_spread_creep: await self.do_queen_micro(unit, air_threats) elif ( self.policy.defend_against_ground and ground_threats and not should_spread_creep ): await self.do_queen_micro(unit, ground_threats) elif self.bot.enemy_units and self.bot.enemy_units.filter( # custom filter to replace in_attack_range_of so that it can be used with memory units lambda enemy: enemy.position.distance_to(unit) < max(unit.air_range, unit.ground_range) ): unit.move(self.policy.rally_point) elif ( unit.energy >= 25 and len(unit.orders) == 0 and self.creep_coverage < self.policy.target_perc_coverage ): await self.spread_creep(unit) elif unit.distance_to(self.policy.rally_point) > 7: if len(unit.orders) > 0: if unit.orders[0].ability.button_name != "CreepTumor": unit.move(self.policy.rally_point) elif len(unit.orders) == 0: unit.move(self.policy.rally_point)
async def attack_command(self, unit: Unit, prism: Unit): self.knowledge.roles.set_task(UnitTask.Reserved, unit) if self.is_revealed_by_enemy(unit): if unit.distance_to( prism) <= 8 and prism.shield_health_percentage >= 0.4: self.do(unit.smart(prism)) return True else: base = self.knowledge.own_main_zone.center_location self.do(unit.move(base)) return True else: await self.attack_priority_targets(unit, prism) return True
def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action: relevant_enemies = self.enemies_near_by.not_flying.visible siege_mode: Optional[AbilityId] = None if self.move_type in { MoveType.PanicRetreat, MoveType.DefensiveRetreat }: if unit.type_id == UnitTypeId.SIEGETANKSIEGED and not relevant_enemies.exists: siege_mode = AbilityId.UNSIEGE_UNSIEGE else: if relevant_enemies.exists: distance = relevant_enemies.closest_distance_to(unit) else: distance = 100 # distance_closest = enemies.closest.distance_to(unit) unsiege_threshold = 15 if self.move_type == MoveType.SearchAndDestroy: unsiege_threshold = 20 status = self.get_siege_status(unit) if unit.type_id == UnitTypeId.SIEGETANK and distance > 5 and distance < 13: # don't siege up on the main base ramp! if unit.distance_to( self.knowledge.enemy_base_ramp.bottom_center) > 7: siege_mode = AbilityId.SIEGEMODE_SIEGEMODE if distance > unsiege_threshold and \ (unit.type_id == UnitTypeId.SIEGETANKSIEGED and distance > unsiege_threshold): siege_mode = AbilityId.UNSIEGE_UNSIEGE if unit.type_id == UnitTypeId.SIEGETANKSIEGED and not relevant_enemies.exists: siege_mode = AbilityId.UNSIEGE_UNSIEGE status = self.get_siege_status(unit) order = status.relay_order(unit, siege_mode, self.ai.time) if order: return order if unit.type_id == UnitTypeId.SIEGETANKSIEGED: return current_command else: return super().unit_solve_combat(unit, current_command)
def get_next_creep_tumor_position(self, tumor: Unit) -> Optional[Point2]: """ Tries to find a suitable position for tumors to move to next. """ tumor_pos: Point2 = tumor.position # TODO Find the closest by ground path instead of air distance target_pos = tumor_pos.closest(self.available_tumor_locations) path = self.knowledge.pathing_manager.path_finder_terrain.find_path( tumor_pos, target_pos)[0] # Skip positions close to the tumor, try to find the location furthest from tumor first for position_tuple in path[:2:-1]: position = Point2(position_tuple) # Although creep tumor have 10 cast range on tumor placements, there are still sometimes errors of 'too far away' if self.is_placeable(position) and self.ai.is_visible( position) and tumor.distance_to(position) < 9: return position # A position could not be found, use the old function to find a location # TODO Investigate why sometimes a tumor location could not be found, ideas: next location is blocked by vision blocker or creeping up a ramp return self.get_next_creep_tumor_position2(tumor)
async def spread_creep(self, queen: Unit, grid: Optional[np.ndarray]) -> None: if self.creep_target_index >= len(self.creep_targets): self.creep_target_index = 0 if self.first_tumor and self.policy.first_tumor_position: queen(AbilityId.BUILD_CREEPTUMOR_QUEEN, self.policy.first_tumor_position) # retry a few times, sometimes queen gets blocked when spawning if self.first_tumor_retry_attempts > 5: self.first_tumor = False self.first_tumor_retry_attempts += 1 return should_lay_tumor: bool = True # if using map_data, creep will follow ground path to the targets if self.map_data: if grid is None: grid = self.map_data.get_pyastar_grid() pos: Point2 = self._find_closest_to_target_using_path( self.creep_targets[self.creep_target_index], self.creep_map, grid) else: pos: Point2 = self._find_closest_to_target( self.creep_targets[self.creep_target_index], self.creep_map) if (not pos or (self.policy.should_tumors_block_expansions is False and self.position_blocks_expansion(pos)) or self.position_near_enemy_townhall(pos) or self.position_near_nydus_worm(pos) or self._existing_tumors_too_close(pos)): should_lay_tumor = False if should_lay_tumor: queen(AbilityId.BUILD_CREEPTUMOR_QUEEN, pos) self.pending_positions.append((pos, self.bot.time)) # can't lay tumor right now, go back home elif queen.distance_to(self.policy.rally_point) > 7: queen.move(self.policy.rally_point) self.creep_target_index += 1
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, priority_enemy_units: Units, unit: Unit, th_tag: int = 0, grid: Optional[np.ndarray] = None, nydus_networks: Optional[Units] = None, nydus_canals: Optional[Units] = None, ) -> None: should_spread_creep: bool = self._check_queen_can_spread_creep(unit) self.creep_targets = self.policy.creep_targets if priority_enemy_units: await self.do_queen_micro(unit, priority_enemy_units, grid) elif (self.policy.defend_against_air and air_threats_near_bases and not should_spread_creep): await self.do_queen_micro(unit, air_threats_near_bases, grid) elif (self.policy.defend_against_ground and ground_threats_near_bases and not should_spread_creep): await self.do_queen_micro(unit, ground_threats_near_bases, grid) elif self.bot.enemy_units and self.bot.enemy_units.filter( # custom filter to replace in_attack_range_of so that it can be used with memory units lambda enemy: enemy.position.distance_to(unit) < max( unit.air_range, unit.ground_range)): unit.move(self.policy.rally_point) elif (unit.energy >= 25 and len(unit.orders) == 0 and self.creep_coverage < self.policy.target_perc_coverage): await self.spread_creep(unit, grid) elif (self.map_data and grid is not None and not self.is_position_safe(grid, unit.position)): await self.move_towards_safe_spot(unit, grid) elif unit.distance_to(self.policy.rally_point) > 7: if len(unit.orders) > 0: if unit.orders[0].ability.button_name != "CreepTumor": unit.move(self.policy.rally_point) elif len(unit.orders) == 0: unit.move(self.policy.rally_point) # check if tumor has been placed at a location yet self._clear_pending_positions()
def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action: if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25: return current_command if self.move_type == MoveType.DefensiveRetreat: if self.ready_to_shoot(unit): closest = self.closest_units.get(unit.tag, None) if closest and closest.is_target: unit_range = self.unit_values.real_range( unit, closest, self.knowledge) if unit_range > 0 and unit_range > unit.distance_to( closest): return Action(closest, True) return current_command elif self.move_type == MoveType.PanicRetreat: return current_command if self.ready_to_shoot(unit): if self.closest_group and self.closest_group.ground_units: current_command = Action(self.closest_group.center, True) else: current_command = Action(current_command.target, True) else: closest = self.closest_units[unit.tag] # d = unit.distance_to(closest) unit_range = self.unit_values.real_range(unit, closest, self.knowledge) - 0.5 if unit.is_flying: best_position = self.pather.find_low_inside_air( unit.position, closest.position, unit_range) else: best_position = self.pather.find_low_inside_ground( unit.position, closest.position, unit_range) return Action(best_position, False) if self.ready_to_shoot(unit) and current_command.is_attack: return self.focus_fire(unit, current_command, self.prio_dict) return current_command
async def handle_unit( self, air_threats_near_bases: Units, ground_threats_near_bases: Units, unit: Unit, priority_enemy_units: Units, th_tag: int = 0, ) -> None: if priority_enemy_units: await self.do_queen_micro(unit, priority_enemy_units) elif self.policy.attack_condition(): await self.do_queen_offensive_micro(unit, self.policy.attack_target) elif self.policy.defend_against_ground and ground_threats_near_bases: await self.do_queen_micro(unit, ground_threats_near_bases) elif self.policy.defend_against_air and air_threats_near_bases: await self.do_queen_micro(unit, air_threats_near_bases) elif unit.distance_to(self.policy.rally_point) > 12: unit.move(self.policy.rally_point)
async def up_and_down(self, harass_prism: Unit): self.knowledge.roles.set_task(UnitTask.Reserved, harass_prism) if harass_prism.has_buff(BuffId.LOCKON): cyclones = self.knowledge.unit_cache.enemy_in_range( harass_prism.position3d, 20).of_type(UnitTypeId.CYCLONE) if cyclones: closest_cyclone = cyclones.closest_to(harass_prism) position = harass_prism.position.towards(closest_cyclone, -18) self.do(harass_prism.move(position)) return True if harass_prism.health_percentage <= 0.2: self.do( harass_prism(AbilityId.UNLOADALLAT_WARPPRISM, harass_prism.position)) return True if harass_prism.distance_to(self.get_enemy_main_platform()) <= 12 and \ (not self.is_revealed_by_enemy(harass_prism)) and \ harass_prism.cargo_used > 0: self.do( harass_prism(AbilityId.UNLOADALLAT_WARPPRISM, harass_prism.position)) return True target = self.get_enemy_main_platform() dts = self.knowledge.unit_cache.by_tags( [self.ninja_dt_1_tag, self.ninja_dt_2_tag]) if dts.exists and harass_prism.cargo_used < 4: target = dts.center for dt in dts: if self.is_revealed_by_enemy(dt): self.do(harass_prism.move(dt.position)) return True self.prism_evasive_move_to(harass_prism, target) return True
def sort_method(unit: Unit): role = self.roles.unit_role(unit) # if self.knowledge.my_race == Race.Protoss and role == UnitTask.Building: # return 0 if unit.distance_to(self.knowledge.enemy_main_zone.behind_mineral_position_center) <= 50: return 10 if role == UnitTask.Idle: return 1 if role == UnitTask.Gathering: if unit.is_gathering and isinstance(unit.order_target, int): target = self.cache.by_tag(unit.order_target) if target and target.is_mineral_field: return 2 else: return 4 if unit.is_carrying_vespene: return 5 if unit.is_carrying_minerals: return 3 return 3 return 10
def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action: if self.move_type == MoveType.DefensiveRetreat or self.move_type == MoveType.PanicRetreat: if self.ready_to_shoot(unit): closest = self.closest_units.get(unit.tag, None) if closest: real_range = self.unit_values.real_range(unit, closest) if 0 < real_range < unit.distance_to(closest): return Action(closest.position, True) return current_command # Phoenixes are generally faster than the rest of the army # if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25: # if self.group.ground_units: # # Regroup with the ground army # return Action(self.group.center, False) has_energy = unit.energy > GRAVITON_BEAM_ENERGY if has_energy and self.allow_lift: best_target: Optional[Unit] = None best_score: float = 0 close_enemies = self.cache.enemy_in_range(unit.position, 14) for enemy in close_enemies: # type: Unit if enemy.is_flying or enemy.is_structure or enemy.has_buff( BuffId.GRAVITONBEAM): continue if self.move_type != MoveType.Harass and enemy.type_id in self.unit_values.worker_types: # If we are not doing any harass, don't lift low priority workers up. # We need to prioritize energy to actual combat units continue pos: Point2 = enemy.position score = self.lift_priority.get( enemy.type_id, -1) + (1 - pos.distance_to(unit) / 10) if score > best_score: best_target = enemy best_score = score if best_target: if best_score > 5 or not close_enemies.flying.exists: self.print( f"Phoenix at {unit.position} lifting {best_target.type_id} at {best_target.position}" ) if unit.distance_to(best_target) > 8: destination = self.knowledge.pathing_manager.find_influence_air_path( unit.position, best_target.position) return Action(destination, False) return Action(best_target, False, AbilityId.GRAVITONBEAM_GRAVITONBEAM) if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25: # Not in combat return current_command targets = self.enemies_near_by.flying if targets: closest = targets.closest_to(unit) # d = unit.distance_to(closest) real_range = self.unit_values.real_range(unit, closest) - 1 best_position = self.pather.find_low_inside_air( unit.position, closest.position, real_range) return Action(best_position, False) return current_command
def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action: if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25: return current_command if self.move_type == MoveType.DefensiveRetreat: if self.ready_to_shoot(unit): closest = self.closest_units.get(unit.tag, None) if closest and self.is_target(closest): range = self.unit_values.real_range(unit, closest) if range > 0 and range > unit.distance_to(closest): return Action(closest, True) return current_command elif self.move_type == MoveType.PanicRetreat: return current_command if self.is_locked_on( unit ) and self.enemies_near_by and not self.ready_to_shoot(unit): cyclones = self.enemies_near_by(UnitTypeId.CYCLONE) if cyclones: closest_cyclone = cyclones.closest_to(unit) backstep: Point2 = closest_cyclone.position.towards( unit.position, 15) if unit.is_flying: backstep = self.pather.find_weak_influence_air(backstep, 4) else: backstep = self.pather.find_weak_influence_ground( backstep, 4) return Action(backstep, False) if self.should_retreat( unit) and self.closest_group and not self.ready_to_shoot(unit): backstep: Point2 = unit.position.towards(self.closest_group.center, -3) if unit.is_flying: backstep = self.pather.find_weak_influence_air(backstep, 4) else: backstep = self.pather.find_weak_influence_ground(backstep, 4) return Action(backstep, False) if self.model == CombatModel.StalkerToSiege: siege_units = self.enemies_near_by.of_type(siege) if siege_units: target = siege_units.closest_to(unit) if target.distance_to(unit) < 7: return Action(target, True) if self.model == CombatModel.StalkerToRoach: if self.ready_to_shoot(unit): if self.closest_group: current_command = Action(self.closest_group.center, True) else: current_command = Action(current_command.target, True) else: closest = self.closest_units[unit.tag] # d = unit.distance_to(closest) range = self.unit_values.real_range(unit, closest) - 0.5 if unit.is_flying: best_position = self.pather.find_low_inside_air( unit.position, closest.position, range) else: best_position = self.pather.find_low_inside_ground( unit.position, closest.position, range) return Action(best_position, False) elif self.model == CombatModel.RoachToStalker: if self.ready_to_shoot(unit): if self.closest_group: current_command = Action(self.closest_group.center, True) else: current_command = Action(current_command.target, True) else: # Instead of backstep, move forward current_command = self.focus_fire(unit, current_command, self.prio_dict) if isinstance(current_command.target, Unit): current_command.target = current_command.target.position current_command.is_attack = False return current_command if self.ready_to_shoot(unit) and current_command.is_attack: return self.focus_fire(unit, current_command, self.prio_dict) return current_command
def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action: if self.force_fielding(unit): # Don't do anything if force field is ordered return NoAction() if ( not self.shield_up and self.should_shield_up and unit.energy >= SHIELD_ENERGY_COST and self.last_shield_up + 0.5 < self.ai.time ): self.shield_up = True self.last_shield_up = self.ai.time return Action(None, False, AbilityId.GUARDIANSHIELD_GUARDIANSHIELD) if unit.shield_percentage < 0.1: if self.range_power > 5 and unit.energy >= HALLUCINATION_ENERGY_COST: return Action(None, False, AbilityId.HALLUCINATION_ARCHON) if self.melee_power > 5 and unit.energy >= FORCE_FIELD_ENERGY_COST: melee = self.knowledge.unit_cache.enemy(self.unit_values.melee) if melee: closest = melee.closest_to(unit) pos = unit.position.towards(closest, 0.6) return Action(pos, False, AbilityId.FORCEFIELD_FORCEFIELD) if self.move_type == MoveType.SearchAndDestroy and unit.energy >= FORCE_FIELD_ENERGY_COST: # Look for defensive force field on ramp or other choke natural: Zone = self.knowledge.expansion_zones[1] main: Zone = self.knowledge.expansion_zones[0] d_natural = unit.distance_to(natural.center_location) d_main = unit.distance_to(main.center_location) if d_natural < 15 and d_natural < d_main and self.closest_group_distance < 10: # Sentry is at the natural zealot_pos: Point2 = self.knowledge.building_solver.zealot_position if self.knowledge.enemy_race == Race.Zerg and natural.our_wall() and zealot_pos: # Protect gate keeper our_keepers = self.cache.own_in_range(zealot_pos, 2).not_structure combined_health = 0 for keeper in our_keepers: # type: Unit combined_health += keeper.health + keeper.shield if combined_health < 70: action = self.should_force_field(zealot_pos.towards(self.closest_group.center, 0.6)) if action: return action if self.model == CombatModel.StalkerToSpeedlings: # Protect buildings buildings = self.cache.own_in_range(unit.position, 8).structure for building in buildings: # type: Unit if building.health + building.shield < 300: action = self.should_force_field(building.position.towards(self.closest_group.center, 1.2)) if action: return action elif not natural.is_ours or natural.power_balance < 0 and d_main < main.radius: # Protect main base ramp not_flying = self.cache.enemy_in_range(self.main_ramp_position, 3).filter( lambda u: not u.is_flying and not u.is_structure ) if not_flying: action = self.should_force_field(self.main_ramp_position) if action: return action # and self.model == CombatModel.StalkerToSpeedlings return super().unit_solve_combat(unit, current_command)
async def _manage_nydus_attack( self, canal: Unit, network: Unit, unit: Unit, unit_distance_to_target: float, grid: Optional[np.ndarray] = None, ) -> None: """ Get a Queen through the nydus and out the other side! @param canal: The canal is the worm placed on the map @param network: This is built at home @param unit: In this case, the queen we want to move through @param unit_distance_to_target: @return: """ # user does not have some predefined nydus logic, so we unload the proxy canal for them if len(canal.passengers_tags) > 0 and not self.policy.nydus_move_function: canal(AbilityId.UNLOADALL_NYDUSWORM) # worm has popped somewhere, but we are waiting for it to finish, move next to network ready to go # usually we want queens last in anyway, so this gives a chance for other units to enter the nydus if not canal.is_ready and unit.distance_to(canal) > 30: unit.move(network.position) # both canal and network must be ready else: # unit needs to go through the nydus if unit_distance_to_target > 45 and unit.distance_to(network) < 70: # user has some custom code for moving units through nydus if self.policy.nydus_move_function: self.policy.nydus_move_function(unit, self.policy.nydus_target) # manage this ourselves else: network(AbilityId.LOAD_NYDUSNETWORK, unit) # else queen should micro on the other side # remember that all queens already have transfuse code baked in else: # queen has enough energy for a transfuse and a tumor, so put a tumor down where she currently is if unit.energy >= 75 and self.bot.has_creep(unit.position): # check if there are too many tumors already tumors: Units = self.bot.structures.filter( lambda s: s.type_id in {UnitID.CREEPTUMORBURROWED, UnitID.CREEPTUMORQUEEN} and s.distance_to(unit) < 15 ) if tumors.amount < 7: unit(AbilityId.BUILD_CREEPTUMOR_QUEEN, unit.position) if unit.is_using_ability(AbilityId.BUILD_CREEPTUMOR_QUEEN): return # get priority target, ie: target the flying enemies first target: Optional[Unit] = self._get_target_from_close_enemies(unit) if target: if self.attack_ready(unit, target): unit.attack(target) elif self.map_data and grid is not None: await self.move_towards_safe_spot(unit, grid) else: distance: float = ( unit.ground_range + unit.radius + target.radius ) move_to: Point2 = target.position.towards(unit, distance) if self.bot.in_pathing_grid(move_to): unit.move(move_to) # check if there is anything around here to attack, # if not then we attack the general attack target the user has passed in # TODO: In the future, this is where we would want the queens to come home # At the moment a nydus queen is on a one way trip elif ( self.enemy_ground_units_near_nydus_target.amount == 0 and self.enemy_flying_units_near_nydus_target.amount == 0 ): await self.do_queen_offensive_micro(unit, self.policy.attack_target) # there are targets, but nothing in range so move towards the nydus target else: unit.move(self.policy.nydus_target)