def use_guardian_shield(self, enemies: EnemyData, sentry: Unit, time: float): if sentry.has_buff(BuffId.GUARDIANSHIELD): return [] if self.tag_shield_used_dict.get(sentry.tag, 0) + self.shield_cooldown < time: for key, value in self.tag_shield_used_dict.items(): if value + 3 < time: # Another sentry just used it's shield, wait a while return [] enemies_at_close_range = enemies.close_enemies.closer_than(GUARDIAN_SHIELD_TRIGGER_RANGE, sentry.position) shooter_power = 0 for enemy in enemies_at_close_range: enemy: Unit = enemy if self.unit_values.is_ranged_unit(enemy): shooter_power += self.unit_values.power(enemy) if shooter_power > 3: self.tag_shield_used_dict[sentry.tag] = time return [CombatAction(sentry, None, False, ability=AbilityId.GUARDIANSHIELD_GUARDIANSHIELD)] return []
def follow_path(self, unit: Unit) -> None: """ Follow the path set or set a new one if none exists. Args: unit (Unit): the unit moving Returns: None """ if unit.tag not in self.pathing_dict: self.add_to_path_dict(unit, self.target) else: advance_factor = int(unit.movement_speed) + 2 self.pathing_dict[unit.tag]["step"] += advance_factor curr_step = self.pathing_dict[unit.tag]["step"] if curr_step >= len(self.pathing_dict[unit.tag]["path"]): curr_step = len(self.pathing_dict[unit.tag]["path"]) - 1 self.do( unit.attack(Point2( self.pathing_dict[unit.tag]["path"][curr_step]))) if curr_step == len(self.pathing_dict[unit.tag]["path"]) - 1: del self.pathing_dict[unit.tag]
def in_attack_range_of(self, unit: Unit, bonus_distance: Union[int, float] = 0) -> Units: """ Filters units that are in attack range of the given unit. This uses the unit and target unit.radius when calculating the distance, so it should be accurate. Caution: This may not work well for static structures (bunker, sieged tank, planetary fortress, photon cannon, spine and spore crawler) because it seems attack ranges differ for static / immovable units. Example:: enemy_zerglings = self.enemy_units(UnitTypeId.ZERGLING) my_marine = next((unit for unit in self.units if unit.type_id == UnitTypeId.MARINE), None) if my_marine: all_zerglings_my_marine_can_attack = enemy_zerglings.in_attack_range_of(my_marine) Example:: enemy_mutalisks = self.enemy_units(UnitTypeId.MUTALISK) my_marauder = next((unit for unit in self.units if unit.type_id == UnitTypeId.MARAUDER), None) if my_marauder: all_mutalisks_my_marauder_can_attack = enemy_mutaliskss.in_attack_range_of(my_marauder) # Is empty because mutalisk are flying and marauder cannot attack air :param unit: :param bonus_distance:""" return self.filter(lambda x: unit.target_in_range(x, bonus_distance=bonus_distance))
def dodge_effects(self, unit: Unit) -> bool: """Dodge any effects""" if not self.ai.state.effects: return False for effect in self.ai.state.effects: if effect.id == GUARDIANSHIELDPERSISTENT: continue effect_data = self.ai.game_data.effects[effect.id] danger_zone = effect_data.radius + unit.radius + .1 closest_effect_position_to_unit = unit.position.closest( effect.positions) if not unit.position.distance_to_point2( closest_effect_position_to_unit) < danger_zone: continue neighbors8_of_unit = list(unit.position.neighbors8) center_of_effect = Point2.center(effect.positions) furthest_neighbor_to_effect = center_of_effect.furthest( neighbors8_of_unit) move_away = -1 * danger_zone self.ai.add_action( unit.move( furthest_neighbor_to_effect.towards( unit.position, move_away))) 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
async def do_queen_micro(self, queen: Unit, enemy: Units) -> None: if not queen or not enemy: return in_range_enemies: Units = enemy.in_attack_range_of(queen) if in_range_enemies: if queen.weapon_cooldown == 0: lowest_hp: Unit = min( in_range_enemies, key=lambda e: (e.health + e.shield, e.tag) ) queen.attack(lowest_hp) else: closest_enemy: Unit = in_range_enemies.closest_to(queen) distance: float = ( queen.ground_range + queen.radius + closest_enemy.radius ) queen.move(closest_enemy.position.towards(queen, distance)) else: queen.attack(enemy.center)
async def do_queen_micro(self, queen: Unit, enemy: Units) -> None: if not queen or not enemy: return in_range_enemies: Units = self.in_attack_range_of(queen, enemy) if in_range_enemies: if queen.weapon_cooldown == 0: target: Unit = self._get_target_from_in_range_enemies( in_range_enemies) queen.attack(target) else: closest_enemy: Unit = self.find_closest_enemy( queen, in_range_enemies) distance: float = (queen.ground_range + queen.radius + closest_enemy.radius) queen.move(closest_enemy.position.towards(queen, distance)) else: target = self.find_closest_enemy(queen, enemy) queen.attack(target)
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
async def do_queen_offensive_micro(self, queen: Unit, offensive_pos: Point2) -> None: if not queen or not offensive_pos: return enemy: Units = self.bot.enemy_units.exclude_type( {UnitID.MULE, UnitID.EGG, UnitID.LARVA}) enemy_structures: Units = self.bot.enemy_structures queens: Units = self.bot.units(UnitID.QUEEN) own_close_queens: Units = queens.filter( lambda u: u.distance_to(queen) < 5) if enemy: in_range_enemies: Units = self.in_attack_range_of(queen, enemy) in_range_structures: Units = self.in_attack_range_of( queen, enemy_structures) if queen.weapon_cooldown == 0: if in_range_enemies: target: Unit = self._get_target_from_in_range_enemies( in_range_enemies) queen.attack(target) elif in_range_structures: queen.attack( self.find_closest_enemy(queen, in_range_structures)) else: queen.move(offensive_pos) else: if own_close_queens.amount <= 3: queen.move(queens.center) else: queen.move(offensive_pos) else: queen.attack(offensive_pos)
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)
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 do_queen_offensive_micro(self, queen: Unit, offensive_pos: Point2) -> None: if not queen or not offensive_pos: return enemy: Units = self.bot.enemy_units.exclude_type( {UnitID.MULE, UnitID.EGG, UnitID.LARVA}) enemy_structures: Units = self.bot.enemy_structures queens: Units = self.bot.units(UnitID.QUEEN) own_close_queens: Units = queens.filter( lambda u: u.distance_to(queen) < 5) if enemy: in_range_enemies: Units = enemy.in_attack_range_of(queen) in_range_structures: Units = enemy_structures.in_attack_range_of( queen) if in_range_enemies: target: Unit = self._get_target_from_in_range_enemies( in_range_enemies) if self.attack_ready(queen, target): queen.attack(target) else: # loose queen_control should try to rejoin the queen pack if own_close_queens.amount <= 3: queen.move(queens.center) # otherwise move forward between attacks, since Queen is slow and can get stuck behind each other else: queen.move(offensive_pos) elif in_range_structures: target: Unit = self._get_target_from_in_range_enemies( in_range_structures) if self.attack_ready(queen, target): queen.attack(target) else: if own_close_queens.amount <= 3: queen.move(queens.center) else: queen.move(offensive_pos) else: queen.move(offensive_pos) else: queen.attack(offensive_pos)
def do_some_unit_property_tests(attacker: Unit, defender: Unit): """ Some tests that are not covered by test_pickled_data.py """ # TODO move unit unrelated tests elsewhere self.step_time self.units_created self.structure_type_build_progress(attacker.type_id) self.structure_type_build_progress(defender.type_id) self.tech_requirement_progress(attacker.type_id) self.tech_requirement_progress(defender.type_id) self.in_map_bounds(attacker.position) self.in_map_bounds(defender.position) self.get_terrain_z_height(attacker.position) self.get_terrain_z_height(defender.position) for unit in [attacker, defender]: unit.shield_percentage unit.shield_health_percentage unit.energy_percentage unit.age_in_frames unit.age unit.is_memory unit.is_snapshot unit.cloak unit.is_revealed unit.can_be_attacked unit.buff_duration_remain unit.buff_duration_max unit.order_target unit.is_transforming unit.has_techlab unit.has_reactor unit.add_on_position unit.health_percentage unit.bonus_damage unit.air_dps attacker.target_in_range(defender) defender.target_in_range(attacker) attacker.calculate_dps_vs_target(defender) defender.calculate_dps_vs_target(attacker) attacker.is_facing(defender) defender.is_facing(attacker) attacker == defender defender == attacker
async def execute_command(self, unit: Unit, command: CombatAction): if command.is_attack: self.do(unit.attack(command.target)) else: self.do(unit.move(command.target))
def _upgrade_building(self, building: Unit, upgrade_to: UnitTypeId): """Upgrade an existing building to another phase (e.g Lair to Hive)""" building.build(upgrade_to)
async def on_building_construction_complete(self, unit: Unit): """ Set rally point of new hatcheries. """ if unit.type_id == UnitTypeId.HATCHERY and self.mineral_field: mf = self.mineral_field.closest_to(unit) unit.smart(mf)
def has_stim(self, unit: Unit) -> bool: return unit.has_buff(BuffId.STIMPACK) or unit.has_buff( BuffId.STIMPACKMARAUDER)
def _prepare_units(self): # Set of enemy units detected by own sensor tower, as blips have less unit information than normal visible units self.blips: Set[Blip] = set() self.all_units: Units = Units([], self) self.units: Units = Units([], self) self.workers: Units = Units([], self) self.larva: Units = Units([], self) self.structures: Units = Units([], self) self.townhalls: Units = Units([], self) self.gas_buildings: Units = Units([], self) self.all_own_units: Units = Units([], self) self.enemy_units: Units = Units([], self) self.enemy_structures: Units = Units([], self) self.all_enemy_units: Units = Units([], self) self.resources: Units = Units([], self) self.destructables: Units = Units([], self) self.watchtowers: Units = Units([], self) self.mineral_field: Units = Units([], self) self.vespene_geyser: Units = Units([], self) self.placeholders: Units = Units([], self) self.techlab_tags: Set[int] = set() self.reactor_tags: Set[int] = set() worker_types: Set[UnitTypeId] = { UnitTypeId.DRONE, UnitTypeId.DRONEBURROWED, UnitTypeId.SCV, UnitTypeId.PROBE } index: int = 0 for unit in self.state.observation_raw.units: if unit.is_blip: self.blips.add(Blip(unit)) else: unit_type: int = unit.unit_type # Convert these units to effects: reaper grenade, parasitic bomb dummy, forcefield if unit_type in FakeEffectID: self.state.effects.add(EffectData(unit, fake=True)) continue unit_obj = Unit(unit, self, distance_calculation_index=index, base_build=self.base_build) index += 1 self.all_units.append(unit_obj) if unit.display_type == IS_PLACEHOLDER: self.placeholders.append(unit_obj) continue alliance = unit.alliance # Alliance.Neutral.value = 3 if alliance == 3: # XELNAGATOWER = 149 if unit_type == 149: self.watchtowers.append(unit_obj) # mineral field enums elif unit_type in mineral_ids: self.mineral_field.append(unit_obj) self.resources.append(unit_obj) # geyser enums elif unit_type in geyser_ids: self.vespene_geyser.append(unit_obj) self.resources.append(unit_obj) # all destructable rocks else: self.destructables.append(unit_obj) # Alliance.Self.value = 1 elif alliance == 1: self.all_own_units.append(unit_obj) unit_id: UnitTypeId = unit_obj.type_id if unit_obj.is_structure: self.structures.append(unit_obj) if unit_id in race_townhalls[self.race]: self.townhalls.append(unit_obj) elif unit_id in ALL_GAS or unit_obj.vespene_contents: # TODO: remove "or unit_obj.vespene_contents" when a new linux client newer than version 4.10.0 is released self.gas_buildings.append(unit_obj) elif unit_id in { UnitTypeId.TECHLAB, UnitTypeId.BARRACKSTECHLAB, UnitTypeId.FACTORYTECHLAB, UnitTypeId.STARPORTTECHLAB, }: self.techlab_tags.add(unit_obj.tag) elif unit_id in { UnitTypeId.REACTOR, UnitTypeId.BARRACKSREACTOR, UnitTypeId.FACTORYREACTOR, UnitTypeId.STARPORTREACTOR, }: self.reactor_tags.add(unit_obj.tag) else: self.units.append(unit_obj) if unit_id in worker_types: self.workers.append(unit_obj) elif unit_id == UnitTypeId.LARVA: self.larva.append(unit_obj) # Alliance.Enemy.value = 4 elif alliance == 4: self.all_enemy_units.append(unit_obj) if unit_obj.is_structure: self.enemy_structures.append(unit_obj) else: self.enemy_units.append(unit_obj) # Force distance calculation and caching on all units using scipy pdist or cdist if self.distance_calculation_method == 1: _ = self._pdist elif self.distance_calculation_method in {2, 3}: _ = self._cdist
def __init__(self, response_observation): self.response_observation = response_observation self.actions = response_observation.actions # successful actions since last loop self.action_errors = response_observation.action_errors # error actions since last loop # https://github.com/Blizzard/s2client-proto/blob/51662231c0965eba47d5183ed0a6336d5ae6b640/s2clientprotocol/sc2api.proto#L575 # TODO: implement alerts https://github.com/Blizzard/s2client-proto/blob/51662231c0965eba47d5183ed0a6336d5ae6b640/s2clientprotocol/sc2api.proto#L640 self.observation = response_observation.observation self.observation_raw = self.observation.raw_data self.dead_units: Set[ int] = self.observation_raw.event.dead_units # returns set of tags of units that died self.alerts = self.observation.alerts self.player_result = response_observation.player_result self.chat = response_observation.chat self.common: Common = Common(self.observation.player_common) # Area covered by Pylons and Warpprisms self.psionic_matrix: PsionicMatrix = PsionicMatrix.from_proto( self.observation_raw.player.power_sources) self.game_loop: int = self.observation.game_loop # 22.4 per second on faster game speed # https://github.com/Blizzard/s2client-proto/blob/33f0ecf615aa06ca845ffe4739ef3133f37265a9/s2clientprotocol/score.proto#L31 self.score: ScoreDetails = ScoreDetails(self.observation.score) self.abilities = self.observation.abilities # abilities of selected units self._blipUnits = [] self.own_units: Units = Units([]) self.enemy_units: Units = Units([]) self.mineral_field: Units = Units([]) self.vespene_geyser: Units = Units([]) self.resources: Units = Units([]) self.destructables: Units = Units([]) self.watchtowers: Units = Units([]) self.units: Units = Units([]) for unit in self.observation.raw_data.units: if unit.is_blip: self._blipUnits.append(unit) else: unit_obj = Unit(unit) self.units.append(unit_obj) alliance = unit.alliance # Alliance.Neutral.value = 3 if alliance == 3: unit_type = unit.unit_type # XELNAGATOWER = 149 if unit_type == 149: self.watchtowers.append(unit_obj) # mineral field enums elif unit_type in mineral_ids: self.mineral_field.append(unit_obj) self.resources.append(unit_obj) # geyser enums elif unit_type in geyser_ids: self.vespene_geyser.append(unit_obj) self.resources.append(unit_obj) # all destructable rocks else: self.destructables.append(unit_obj) # Alliance.Self.value = 1 elif alliance == 1: self.own_units.append(unit_obj) # Alliance.Enemy.value = 4 elif alliance == 4: self.enemy_units.append(unit_obj) self.upgrades: Set[UpgradeId] = { UpgradeId(upgrade) for upgrade in self.observation_raw.player.upgrade_ids } # Set of unit tags that died this step self.dead_units: Set[int] = { dead_unit_tag for dead_unit_tag in self.observation_raw.event.dead_units } # Set of enemy units detected by own sensor tower, as blips have less unit information than normal visible units self.blips: Set[Blip] = {Blip(unit) for unit in self._blipUnits} # self.visibility[point]: 0=Hidden, 1=Fogged, 2=Visible self.visibility: PixelMap = PixelMap( self.observation_raw.map_state.visibility, mirrored=True) # HSPARK 수정 시작 # self.creep[point]: 0=No creep, 1=creep # self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep, mirrored=True) self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep, mirrored=True, in_bits=True) # HSPARK 수정 끝 # Effects like ravager bile shot, lurker attack, everything in effect_id.py self.effects: Set[EffectData] = { EffectData(effect) for effect in self.observation_raw.effects } """ Usage:
def from_proto(cls, units, bot_object: BotAI): # pylint: disable=E1120 return cls((Unit(raw_unit, bot_object=bot_object) for raw_unit in units))
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 is_locked_on(self, unit: Unit) -> bool: if unit.has_buff(BuffId.LOCKON): return True return False
def focus_fire(step: MicroStep, unit: Unit, current_command: Action, prio: Optional[Dict[UnitTypeId, int]]) -> Action: shoot_air = step.unit_values.can_shoot_air(unit) shoot_ground = step.unit_values.can_shoot_ground(unit) air_range = step.unit_values.air_range(unit) ground_range = step.unit_values.ground_range(unit) lookup = min(air_range + 3, ground_range + 3) enemies = step.cache.enemy_in_range(unit.position, lookup) last_target = step.last_targeted(unit) if not enemies: # No enemies to shoot at return current_command value_func: Callable[[Unit], float] if prio: value_func = ( lambda u: 1 if u.type_id in changelings else prio.get(u.type_id, -1) * (1 - u.shield_health_percentage)) else: value_func = ( lambda u: 1 if u.type_id in changelings else 2 * step.unit_values. power_by_type(u.type_id, 1 - u.shield_health_percentage)) best_target: Optional[Unit] = None best_score: float = 0 for enemy in enemies: # type: Unit if enemy.type_id in ignored_types: continue if not step.is_target(enemy): continue if not shoot_air and enemy.is_flying: continue if not shoot_ground and not enemy.is_flying: continue pos: Point2 = enemy.position score = value_func(enemy) + (1 - pos.distance_to(unit) / lookup) if enemy.tag == last_target: score += 3 if step.focus_fired.get(enemy.tag, 0) > enemy.health: score *= 0.1 if score > best_score: best_target = enemy best_score = score if best_target: step.focus_fired[best_target.tag] = ( step.focus_fired.get(best_target.tag, 0) + unit.calculate_damage_vs_target(best_target)[0]) return Action(best_target, True) return current_command
def one_of_targets_in_range(self, unit: Unit, targets: Units): for target in targets: if unit.target_in_range(target): return True return False