def actions( self ) -> List[Union[ActionRawUnitCommand, ActionRawToggleAutocast, ActionRawCameraMove]]: """ List of successful actions since last frame. See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L630-L637 Each action is converted into Python dataclasses: ActionRawUnitCommand, ActionRawToggleAutocast, ActionRawCameraMove """ previous_frame_actions = self.previous_observation.actions if self.previous_observation else [] actions = [] for action in chain(previous_frame_actions, self.response_observation.actions): action_raw = action.action_raw game_loop = action.game_loop if action_raw.HasField("unit_command"): # Unit commands raw_unit_command = action_raw.unit_command if raw_unit_command.HasField("target_world_space_pos"): # Actions that have a point as target actions.append( ActionRawUnitCommand( game_loop, raw_unit_command.ability_id, raw_unit_command.unit_tags, raw_unit_command.queue_command, Point2.from_proto( raw_unit_command.target_world_space_pos), )) else: # Actions that have a unit as target actions.append( ActionRawUnitCommand( game_loop, raw_unit_command.ability_id, raw_unit_command.unit_tags, raw_unit_command.queue_command, None, raw_unit_command.target_unit_tag, )) elif action_raw.HasField("toggle_autocast"): # Toggle autocast actions raw_toggle_autocast_action = action_raw.toggle_autocast actions.append( ActionRawToggleAutocast( game_loop, raw_toggle_autocast_action.ability_id, raw_toggle_autocast_action.unit_tags, )) else: # Camera move actions actions.append( ActionRawCameraMove( Point2.from_proto( action.action_raw.camera_move.center_world_space))) return actions
def __init__(self, proto): self._proto = proto self.players: List[Player] = [ Player.from_proto(p) for p in self._proto.player_info ] self.map_name: str = self._proto.map_name self.local_map_path: str = self._proto.local_map_path self.map_size: Size = Size.from_proto(self._proto.start_raw.map_size) # self.pathing_grid[point]: if 0, point is not pathable, if 1, point is pathable self.pathing_grid: PixelMap = PixelMap( self._proto.start_raw.pathing_grid, in_bits=True, mirrored=False) # self.terrain_height[point]: returns the height in range of 0 to 255 at that point self.terrain_height: PixelMap = PixelMap( self._proto.start_raw.terrain_height, mirrored=False) # self.placement_grid[point]: if 0, point is not placeable, if 1, point is pathable self.placement_grid: PixelMap = PixelMap( self._proto.start_raw.placement_grid, in_bits=True, mirrored=False) self.playable_area = Rect.from_proto( self._proto.start_raw.playable_area) self.map_center = self.playable_area.center self.map_ramps: List[ Ramp] = None # Filled later by BotAI._prepare_first_step self.vision_blockers: FrozenSet[ Point2] = None # Filled later by BotAI._prepare_first_step self.player_races: Dict[int, Race] = { p.player_id: p.race_actual or p.race_requested for p in self._proto.player_info } self.start_locations: List[Point2] = [ Point2.from_proto(sl) for sl in self._proto.start_raw.start_locations ] self.player_start_location: Point2 = None # Filled later by BotAI._prepare_first_step
def pending_building_positions(self, unit_type: UnitTypeId) -> List[Point2]: """Returns positions of buildings of the specified type that have either been ordered to be built by a worker or are currently being built.""" positions: List[Point2] = list() creation_ability: AbilityId = self.ai._game_data.units[ unit_type.value].creation_ability # Workers ordered to build for worker in self.ai.workers: worker: Unit = worker for order in worker.orders: order: UnitOrder = order if order.ability.id == creation_ability.id: p2: Point2 = Point2.from_proto(order.target) positions.append(p2) # Already building structures # Avoid counting structures twice for Terran SCVs. if self.knowledge.my_race != Race.Terran: pending_buildings: List[Point2] = list( map(lambda structure: structure.position, self.cache.own(unit_type).structure.not_ready)) positions.extend(pending_buildings) return positions
def solve_combat(self, goal: CombatGoal, command: CombatAction, enemies: EnemyData) -> List[CombatAction]: sentry = goal.unit time = self.knowledge.ai.time if goal.unit.orders and goal.unit.orders[0].ability.id == AbilityId.FORCEFIELD_FORCEFIELD: return [CombatAction(sentry, Point2.from_proto(goal.unit.orders[0].target), False, ability=AbilityId.FORCEFIELD_FORCEFIELD)] if sentry.energy < FORCE_FIELD_ENERGY_COST: return [] # do nothing if self.knowledge.expansion_zones[1].is_ours and self.knowledge.expansion_zones[1].is_under_attack: # TODO: Should we force field here? ... else: not_flying = enemies.close_enemies.filter(lambda u: not u.is_flying and not u.is_structure) if not_flying: closest_to_ramp = not_flying.closest_to(self.main_ramp_position) closest_to_ramp_distance = closest_to_ramp.distance_to(self.main_ramp_position) if closest_to_ramp_distance < 7 \ and self.ai.get_terrain_height(sentry) > self.ai.get_terrain_height(closest_to_ramp)\ and sentry.distance_to(self.main_ramp_position) < 9: if self.ramp_field_used + self.force_field_cooldown < time: if closest_to_ramp_distance < 2: self.ramp_field_used = time return [CombatAction(sentry, self.main_ramp_position, False, ability= AbilityId.FORCEFIELD_FORCEFIELD)] return [] if self.last_hallu_time + self.hallu_timer < time and sentry.energy >= HALLUCINATION_ENERGY_COST and enemies.close_enemies.exists: hallu_type: Optional[AbilityId] = None if self.knowledge.enemy_race == Race.Terran: if enemies.close_enemies.of_type([UnitTypeId.BANSHEE, UnitTypeId.BATTLECRUISER]): hallu_type = AbilityId.HALLUCINATION_STALKER elif enemies.close_enemies.of_type([UnitTypeId.VIKINGFIGHTER, UnitTypeId.RAVEN]): hallu_type = AbilityId.HALLUCINATION_COLOSSUS elif sentry.health + sentry.shield < 30: hallu_type = AbilityId.HALLUCINATION_IMMORTAL elif self.knowledge.enemy_race == Race.Zerg: if enemies.close_enemies(UnitTypeId.HYDRALISK): hallu_type = AbilityId.HALLUCINATION_VOIDRAY if enemies.close_enemies(UnitTypeId.ROACH) or sentry.health + sentry.shield < 30: hallu_type = AbilityId.HALLUCINATION_IMMORTAL else: if sentry.health + sentry.shield < 30: hallu_type = AbilityId.HALLUCINATION_IMMORTAL if hallu_type is not None: self.last_hallu_time = time return [CombatAction(sentry, None, False, ability=hallu_type)] result = self.use_guardian_shield(enemies, sentry, time) if len(result) > 0: return result return self.use_force_field(enemies, sentry, time)
async def scout_with_percentage_of_army(self, percentage, use_overlords, pull_back_if_damaged): map_width = self.mainAgent.map_width map_height = self.mainAgent.map_height army = self.army if use_overlords: army += self.mainAgent.units(OVERLORD) desired_strike_force_size = int(percentage * army.amount) if self.mainAgent.strike_force is None: self.mainAgent.strike_force = army.take(desired_strike_force_size) # If strike force should include more members (If a unit was built) # Do not add more units if the entire army is already in strike force if len(self.mainAgent.strike_force ) < desired_strike_force_size and len(army) > len( self.mainAgent.strike_force): self.mainAgent.strike_force += ( army - self.mainAgent.strike_force ).take(desired_strike_force_size - len(self.mainAgent.strike_force)) for unit_ref in self.mainAgent.strike_force: # Need to reacquire unit from self.mainAgent.units to see that a command has been queued id = unit_ref.tag unit = self.mainAgent.units.find_by_tag(id) if unit is None: # Unit died self.mainAgent.strike_force.remove(unit_ref) continue if pull_back_if_damaged and unit.health < unit.health_max: # If pull_back is true and unti is damaged, move to random hatchery if (len(self.mainAgent.bases) > 0): await self.mainAgent.do( unit.move(self.mainAgent.bases[random.randrange( 0, len(self.mainAgent.bases))].position)) elif unit.noqueue: # Go to a new random position pos = lambda: None # https://stackoverflow.com/questions/19476816/creating-an-empty-object-in-python pos.x = random.randrange(0, map_width) pos.y = random.randrange(0, map_height) position_to_search = Point2.from_proto(pos) await self.mainAgent.do(unit.move(position_to_search))
def _worker_orders(self) -> Counter: """ This function is used internally, do not use! It is to store all worker abilities. """ abilities_amount = Counter() structures_in_production: Set[Union[Point2, int]] = set() for structure in self.structures: if structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV: structures_in_production.add(structure.position) structures_in_production.add(structure.tag) for worker in self.workers: for order in worker.orders: # Skip if the SCV is constructing (not isinstance(order.target, int)) # or resuming construction (isinstance(order.target, int)) is_int = isinstance(order.target, int) if (is_int and order.target in structures_in_production or not is_int and Point2.from_proto( order.target) in structures_in_production): continue abilities_amount[order.ability] += 1 return abilities_amount
async def perform_strategy(self, iteration, strategy_num): self.mainAgent.clean_strike_force( ) # Clear dead units from strike force self.mainAgent.is_army_cached = False # Must re obtain army data if self.mainAgent.predicted_enemy_position_num == -1: # Initializing things that are needed after game data is loaded # Prevent game from crashing hatchery = self.mainAgent.bases if hatchery == None or hatchery.amount == 0: return else: hatchery = self.mainAgent.bases.ready.random # Assume first position self.mainAgent.predicted_enemy_position = 0 self.mainAgent.num_enemy_positions = len( self.mainAgent.enemy_start_locations) self.mainAgent.start_location = self.mainAgent.bases.ready.random.position # Should only be 1 hatchery at this time self.mainAgent.map_width = self.mainAgent.game_info.map_size[0] self.mainAgent.map_height = self.mainAgent.game_info.map_size[1] # Get a point in the corner of the map p = lambda: None # https://stackoverflow.com/questions/19476816/creating-an-empty-object-in-python p.x = self.mainAgent.game_info.map_center.x * 1.9 p.y = self.mainAgent.game_info.map_center.y * 1.9 self.mainAgent.map_corner = Point2.from_proto(p) # Make sure given strategy num is valid if Strategies.has_value(strategy_num): # Valid strategy num, convert int into enum value strategy = Strategies(strategy_num) # Mark strategy as changed or not if strategy != self.mainAgent.prev_strategy: self.mainAgent.log("New strategy is " + str(strategy)) self.mainAgent.did_strategy_change = True self.mainAgent.strike_force = None else: self.mainAgent.did_strategy_change = False self.mainAgent.prev_strategy = strategy # Prepare for next iteration else: self.log_error(f"Unknown strategy number {strategy_num}") return # Call the proper strategy function # Prevent game from crashing hatchery = self.mainAgent.bases if hatchery == None or hatchery.amount == 0: return else: hatchery = self.mainAgent.bases.ready.random # Attack if strategy == Strategies.HEAVY_ATTACK: await self.heavy_attack(iteration) elif strategy == Strategies.MEDIUM_ATTACK: await self.medium_attack(iteration) elif strategy == Strategies.LIGHT_ATTACK: await self.light_attack(iteration) # Scouting elif strategy == Strategies.HEAVY_SCOUTING: await self.heavy_scouting(iteration) elif strategy == Strategies.MEDIUM_SCOUTING: await self.medium_scouting(iteration) elif strategy == Strategies.LIGHT_SCOUTING: await self.light_scouting(iteration) # Defense elif strategy == Strategies.HEAVY_DEFENSE: await self.heavy_defense(iteration) elif strategy == Strategies.MEDIUM_DEFENSE: await self.medium_defense(iteration) elif strategy == Strategies.LIGHT_DEFENSE: await self.light_defense(iteration) # Harass elif strategy == Strategies.HEAVY_HARASS: await self.heavy_harass(iteration) elif strategy == Strategies.MEDIUM_HARASS: await self.medium_harass(iteration) elif strategy == Strategies.LIGHT_HARASS: await self.light_harass(iteration) # Unknown else: self.log("Unknown strategy was given: " + str(strategy))
def from_proto(cls, proto): return cls(Point2.from_proto(proto.pos), proto.radius, proto.tag)
def position(self) -> Point2: """2d position of the blip.""" return Point2.from_proto(self._proto.pos)
def positions(self) -> Set[Point2]: if self.fake: return {Point2.from_proto(self._proto.pos)} return {Point2.from_proto(p) for p in self._proto.pos}