Exemple #1
0
    def _defenders_from(self, task: UnitTask, current_power: ExtendedPower, position: Point2, power: ExtendedPower, units: Units):
        """ Get defenders from a task. """
        if current_power.is_enough_for(power):
            return

        exclude_types = []
        exclude_types.append(UnitTypeId.OVERSEER)
        exclude_types.extend(self.knowledge.unit_values.worker_types)
        exclude_types.extend(self.peace_unit_types)

        role_units = self.roles[task.value].units\
            .exclude_type(exclude_types)

        unit: Unit
        for unit in role_units.sorted_by_distance_to(position):
            enough_air_power = current_power.air_power >= power.air_presence * 1.1
            enough_ground_power = current_power.ground_power >= power.ground_presence * 1.1

            if not self.unit_values.can_shoot_air(unit) and not enough_air_power and enough_ground_power:
                # Don't pull any more units that can't actually shoot the targets
                continue

            if not self.unit_values.can_shoot_ground(unit, self.knowledge) and enough_air_power and not enough_ground_power:
                # Don't pull any more units that can't actually shoot the targets
                continue

            current_power.add_unit(unit)
            units.append(unit)
            if current_power.is_enough_for(power):
                return
        return
    def enemy_total_power(self) -> ExtendedPower:
        """Returns the total power of all enemy units we currently know about.
         Assumes they are all in full health. Ignores workers and overlords."""
        total_power = ExtendedPower(self.unit_value)
        for type_id in self._known_enemy_units_dict:
            if self.unit_value.is_worker(type_id):
                continue

            if type_id == UnitTypeId.OVERLORD:
                continue

            count_for_unit_type = self.unit_count(type_id)
            total_power.add_unit(type_id, count_for_unit_type)

        return total_power
Exemple #3
0
class EnemyData:
    close_enemies: Units
    enemy_center: Point2
    closest: Unit

    def __init__(self, knowledge: Knowledge, close_enemies: Units, unit: Unit, our_units: Units, our_median: Point2):
        self.ai = knowledge.ai
        self.our_units = our_units
        self.our_median = our_median

        self.close_enemies = close_enemies
        self.my_height = self.ai.get_terrain_height(unit)
        self.enemy_power = ExtendedPower(knowledge.unit_values)
        self.our_power = ExtendedPower(knowledge.unit_values)

        for unit in our_units: # type: Unit
            self.our_power.add_unit(unit)

        self.worker_only = False

        if self.close_enemies.exists:
            self.enemy_center = close_enemies.center
            # Can be empty!
            self.powered_enemies = close_enemies.filter(lambda x: knowledge.unit_values.power(x) > 0.1)

            if self.powered_enemies.exists:
                self.closest = unit.position.closest(self.powered_enemies)
                self.worker_only = True
                for enemy in self.powered_enemies: # type: Unit
                    if not knowledge.unit_values.is_worker(enemy):
                        self.worker_only = False
                    self.enemy_power.add_unit(enemy)
            else:
                self.closest = unit.position.closest(self.close_enemies)

            self.enemy_center_height = self.ai.get_terrain_height(self.enemy_center)
            self.closest_height = self.ai.get_terrain_height(self.closest)
        else:
            self.powered_enemies = Units([], self.ai) # empty list

    @property
    def enemies_exist(self) -> bool:
        return self.close_enemies.exists
Exemple #4
0
    def solve_combat(self, goal: CombatGoal, command: CombatAction, enemies: EnemyData) -> List[CombatAction]:
        oracle = goal.unit

        if not oracle.has_buff(BuffId.ORACLEWEAPON):
            goal.ready_to_shoot = False

        air_shooter_enemies = Units([], self.ai)
        enemy: Unit

        power = ExtendedPower(self.unit_values)

        for enemy in enemies.close_enemies:
            if self.unit_values.air_range(enemy) < enemy.distance_to(oracle) + 1:
                air_shooter_enemies.append(enemy)
                power.add_unit(enemy)
                if self.unit_values.is_static_air_defense(enemy):
                    power.add(5) # can't beat turrets with oracle

        enemy_center = enemies.close_enemies.center

        for air_shooter in air_shooter_enemies:  # type: Unit
            if air_shooter.is_light and not air_shooter.is_flying:
                power.add_units(air_shooter_enemies)
            else:
                power.add_units(air_shooter_enemies * 2)

        time = self.knowledge.ai.time
        if goal.move_type == MoveType.PanicRetreat and oracle.has_buff(BuffId.ORACLEWEAPON):
            return self.disable_beam(oracle)

        possible_targets = enemies.close_enemies.filter(lambda u: not u.is_flying and not u.is_structure and u.is_light)

        if possible_targets.exists:
            if oracle.energy > 50 and possible_targets.closest_distance_to(oracle) < 5 and not oracle.has_buff(BuffId.ORACLEWEAPON):
                return self.enable_beam(oracle)

        if  power.air_power > 0 and power.air_power <= 3:
            target = air_shooter_enemies.closest_to(oracle)
            if target.is_light or target.health_percentage < 0.5:
                if not oracle.has_buff(BuffId.ORACLEWEAPON):
                    return self.enable_beam(oracle)
                # Kill the target
                return [CombatAction(oracle, target, True)]

            #target_pos = self.knowledge.pathing_manager.find_weak_influence_air(goal.target, 7)
            #move_step = self.knowledge.pathing_manager.find_influence_air_path(oracle.position, target_pos)
            return [CombatAction(oracle, target.position, True)]
        elif goal.ready_to_shoot and possible_targets:
            return [CombatAction(oracle, possible_targets.closest_to(oracle), True)]
        elif power.air_power > 12:
            # Panic retreat to whatever direction
            if goal.move_type in offensive:
                new_target: Point2 = self.knowledge.pathing_manager.find_weak_influence_air(goal.target, 7)
                step = self.knowledge.pathing_manager.find_influence_air_path(oracle.position, new_target)
                # backstep: Point2 = self.knowledge.pathing_manager.find_weak_influence_air(oracle.position, 7)
                move_action = CombatAction(oracle, step, False)
            else:
                backstep = self.knowledge.pathing_manager.find_influence_air_path(oracle.position, goal.target)
                move_action = CombatAction(oracle, backstep, False)

            # Todo disable beam?
            return [move_action]

        elif power.air_power > 3:
            # Try kiting while killing the target
            target = self.knowledge.pathing_manager.find_weak_influence_air(goal.target, 7)
            backstep = self.knowledge.pathing_manager.find_influence_air_path(oracle.position, target)

            if goal.move_type in offensive:
                move_action = CombatAction(oracle, backstep, False)
            else:
                move_action = CombatAction(oracle, backstep, False)

            if oracle.has_buff(BuffId.ORACLEWEAPON):
                if possible_targets:
                    closest = possible_targets.closest_to(oracle)
                    if closest.distance_to(oracle) < 5:
                        return [CombatAction(oracle, closest, True)]
                return [CombatAction(oracle, command.target, True), move_action]
            else:
                return [move_action]


        if possible_targets.exists:
            return [CombatAction(oracle, command.target, True)]
        else:
            return [CombatAction(oracle, command.target, False)]
Exemple #5
0
    async def execute(self) -> bool:
        unit: Unit

        all_defenders = self.knowledge.roles.all_from_task(UnitTask.Defending)

        for i in range(0, len(self.knowledge.expansion_zones)):
            zone: 'Zone' = self.knowledge.expansion_zones[i]
            zone_tags = self.defender_tags[i]

            zone_defenders_all = all_defenders.tags_in(zone_tags)
            zone_worker_defenders = zone_defenders_all(self.worker_type)
            zone_defenders = zone_defenders_all.exclude_type(self.worker_type)
            enemies = zone.known_enemy_units

            # Let's loop zone starting from our main, which is the one we want to defend the most
            # Check that zone is either in our control or is our start location that has no Nexus
            if zone_defenders.exists or zone.is_ours or zone == self.knowledge.own_main_zone:
                if not self.defense_required(enemies):
                    # Delay before removing defenses in case we just lost visibility of the enemies
                    if zone.last_scouted_center == self.knowledge.ai.time \
                            or self.zone_seen_enemy[i] + PlanZoneDefense.ZONE_CLEAR_TIMEOUT < self.ai.time:
                        self.knowledge.roles.clear_tasks(zone_defenders_all)
                        zone_defenders.clear()
                        zone_tags.clear()
                        continue  # Zone is well under control.
                else:
                    self.zone_seen_enemy[i] = self.ai.time

                if enemies.exists:
                    # enemy_center = zone.assaulting_enemies.center
                    enemy_center = enemies.closest_to(
                        zone.center_location).position
                elif zone.assaulting_enemies:
                    enemy_center = zone.assaulting_enemies.closest_to(
                        zone.center_location).position
                else:
                    enemy_center = zone.gather_point

                defense_required = ExtendedPower(self.unit_values)
                defense_required.add_power(zone.assaulting_enemy_power)
                defense_required.multiply(1.5)

                defenders = ExtendedPower(self.unit_values)

                for unit in zone_defenders:
                    self.combat.add_unit(unit)
                    defenders.add_unit(unit)

                # Add units to defenders that are being warped in.
                for unit in self.knowledge.roles.units(
                        UnitTask.Idle).not_ready:
                    if unit.distance_to(zone.center_location) < zone.radius:
                        # unit is idle in the zone, add to defenders
                        self.combat.add_unit(unit)
                        self.knowledge.roles.set_task(UnitTask.Defending, unit)
                        zone_tags.append(unit.tag)

                if not defenders.is_enough_for(defense_required):
                    defense_required.substract_power(defenders)
                    for unit in self.knowledge.roles.get_defenders(
                            defense_required, zone.center_location):
                        if unit.distance_to(
                                zone.center_location) < zone.radius:
                            # Only count units that are close as defenders
                            defenders.add_unit(unit)

                        self.knowledge.roles.set_task(UnitTask.Defending, unit)
                        self.combat.add_unit(unit)
                        zone_tags.append(unit.tag)

                if len(enemies) > 1 or (len(enemies) == 1
                                        and enemies[0].type_id
                                        not in self.unit_values.worker_types):
                    # Pull workers to defend only and only if the enemy isn't one worker scout
                    if defenders.is_enough_for(defense_required):
                        # Workers should return to mining.
                        for unit in zone_worker_defenders:
                            zone.go_mine(unit)
                            if unit.tag in zone_tags:  # Just in case, should be in zone tags always.
                                zone_tags.remove(unit.tag)
                        # Zone is well under control without worker defense.
                    else:
                        await self.worker_defence(defenders.power,
                                                  defense_required,
                                                  enemy_center, zone,
                                                  zone_tags,
                                                  zone_worker_defenders)

                self.combat.execute(enemy_center, MoveType.SearchAndDestroy)
        return True  # never block
Exemple #6
0
 def enemy_static_air_power(self) -> ExtendedPower:
     """Returns power of enemy static ground defenses on the zone."""
     power = ExtendedPower(self.unit_values)
     for air_def in self.enemy_static_air_defenses:
         power.add_unit(air_def)
     return power
Exemple #7
0
 def enemy_static_ground_power(self) -> ExtendedPower:
     """Returns power of enemy static ground defenses."""
     power = ExtendedPower(self.unit_values)
     for ground_def in self.enemy_static_ground_defenses:
         power.add_unit(ground_def)
     return power
    async def update_influence(self):
        power = ExtendedPower(self.unit_values)
        self.path_finder_terrain.reset()  # Reset
        self.path_finder_ground.reset()  # Reset

        positions = []
        for mf in self.ai.mineral_field:  # type: Unit
            # In 4.8.5+ minerals are no linger visible in pathing grid
            positions.append(mf.position)

        # for mf in self.ai.mineral_walls:  # type: Unit
        #     # In 4.8.5+ minerals are no linger visible in pathing grid
        #     positions.append(mf.position)

        self.path_finder_terrain.create_block(positions, (2, 1))
        self.path_finder_ground.create_block(positions, (2, 1))

        self.set_rocks(self.path_finder_terrain)
        self.set_rocks(self.path_finder_ground)

        for building in self.ai.structures + self.knowledge.known_enemy_structures:  # type: Unit
            if building.type_id in buildings_2x2:
                self.path_finder_ground.create_block(building.position, (2, 2))
            elif building.type_id in buildings_3x3:
                self.path_finder_ground.create_block(building.position, (3, 3))
            elif building.type_id in buildings_5x5:
                self.path_finder_ground.create_block(building.position, (5, 3))
                self.path_finder_ground.create_block(building.position, (3, 5))

        self.set_rocks(self.path_finder_ground)

        self.path_finder_ground.normalize_influence(20)
        self.path_finder_air.normalize_influence(20)

        for enemy_type in self.cache.enemy_unit_cache:  # type: UnitTypeId
            enemies: Units = self.cache.enemy_unit_cache.get(
                enemy_type, Units([], self.ai))
            if len(enemies) == 0:
                continue

            example_enemy: Unit = enemies[0]
            power.clear()
            power.add_unit(enemy_type, 100)

            if self.unit_values.can_shoot_air(example_enemy):
                positions: List[Point2] = map(
                    lambda u: u.position,
                    enemies)  # need to be specified in both places
                s_range = self.unit_values.air_range(example_enemy)

                if example_enemy.type_id == UnitTypeId.CYCLONE:
                    s_range = 7

                self.path_finder_air.add_influence(positions, power.air_power,
                                                   s_range + 3)

            if self.unit_values.can_shoot_ground(example_enemy,
                                                 self.knowledge):
                positions = map(lambda u: u.position,
                                enemies)  # need to be specified in both places
                s_range = self.unit_values.ground_range(
                    example_enemy, self.knowledge)
                if example_enemy.type_id == UnitTypeId.CYCLONE:
                    s_range = 7

                if s_range < 2:
                    self.path_finder_ground.add_influence_walk(
                        positions, power.ground_power, 7)
                elif s_range < 5:
                    self.path_finder_ground.add_influence_walk(
                        positions, power.ground_power, 7)
                else:
                    self.path_finder_ground.add_influence(
                        positions, power.ground_power, s_range + 3)

        # influence, radius, points, can it hit air?
        effect_dict: Dict[EffectId, Tuple[float, float, List[Point2],
                                          bool]] = dict()
        for effect in self.ai.state.effects:
            values: Tuple[float, float, List[Point2], bool] = None

            if effect.id == EffectId.RAVAGERCORROSIVEBILECP:
                values = effect_dict.get(effect.id, (1000, 2.5, [], True))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.BLINDINGCLOUDCP:
                values = effect_dict.get(effect.id, (400, 3.5, [], False))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.NUKEPERSISTENT:
                values = effect_dict.get(effect.id, (900, 9, [], True))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.PSISTORMPERSISTENT:
                values = effect_dict.get(effect.id, (300, 3.5, [], True))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.LIBERATORTARGETMORPHDELAYPERSISTENT:
                values = effect_dict.get(effect.id, (200, 6, [], False))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.LIBERATORTARGETMORPHPERSISTENT:
                values = effect_dict.get(effect.id, (300, 6, [], False))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.LURKERMP:
                # Each lurker spine deals splash damage to a radius of 0.5
                values = effect_dict.get(effect.id, (1000, 1, [], False))
                values[2].extend(effect.positions)

            if values is not None and effect.id not in effect_dict:
                effect_dict[effect.id] = values

        for effects in effect_dict.values():
            if effects[3]:
                self.path_finder_air.add_influence(effects[2], effects[0],
                                                   effects[1])
            self.path_finder_ground.add_influence(effects[2], effects[0],
                                                  effects[1])
class EnemyArmyPredicter(ManagerBase):
    def __init__(self):
        super().__init__()

    async def start(self, knowledge: 'Knowledge'):
        await super().start(knowledge)
        self.enemy_units_manager: EnemyUnitsManager = self.knowledge.enemy_units_manager

        self.lost_units_manager: LostUnitsManager = knowledge.lost_units_manager
        self.unit_values: 'UnitValue' = knowledge.unit_values

        self.updater = IntervalFuncAsync(self.ai, self._real_update, INTERVAL)

        self.enemy_base_value_minerals = 400 + 12 * 50 + 50
        self.enemy_known_worker_count = 12

        self.mineral_dict: Dict['Zone', int] = {}

        # Last time minerals were updated
        self.mineral_updated_dict: Dict['Zone', float] = {}
        self.gas_dict: Dict[Point2, int] = {}

        for zone in knowledge.expansion_zones:
            minerals = 0
            if zone.last_minerals is not None:
                minerals = zone.last_minerals

            self.mineral_dict[zone] = minerals

        for geyser in self.ai.vespene_geyser:  # type: Unit
            self.gas_dict[geyser.position] = 2250

        self.enemy_mined_minerals = 0
        self.enemy_mined_minerals_prediction = 0
        self.enemy_mined_gas = 0

        self.enemy_army_known_minerals = 0
        self.enemy_army_known_gas = 0

        self.own_army_value_minerals = 0
        self.own_army_value_gas = 0

        self.predicted_enemy_free_minerals = 0
        self.predicted_enemy_free_gas = 0

        self.predicted_enemy_army_minerals = 0
        self.predicted_enemy_army_gas = 0

        self.predicted_enemy_composition: List[UnitCount] = []

        self.enemy_power = ExtendedPower(self.unit_values)
        self.predicted_enemy_power = ExtendedPower(self.unit_values)

    @property
    def own_value(self):
        """ Our exact army value that we know of """
        return self.own_army_value_minerals + self.own_army_value_gas

    @property
    def enemy_value(self):
        """ Best estimation on how big value enemy army has """
        return self.predicted_enemy_army_minerals + self.predicted_enemy_army_gas

    async def update(self):
        await self.updater.execute()

    async def _real_update(self):
        await self.update_own_army_value()

        self.enemy_power.clear()
        self.predicted_enemy_power.clear()

        self.predicted_enemy_composition.clear()
        gas_miners = self.knowledge.known_enemy_structures.of_type([
            UnitTypeId.ASSIMILATOR, UnitTypeId.EXTRACTOR, UnitTypeId.REFINERY
        ])

        minerals_used: int = 0
        gas_used: int = 0
        enemy_composition = self.enemy_units_manager.enemy_composition
        self.enemy_known_worker_count = 0

        self.enemy_army_known_minerals = 0
        self.enemy_army_known_gas = 0

        self.predicted_enemy_free_minerals = 0
        self.predicted_enemy_free_gas = 0

        self.predicted_enemy_army_minerals = 0
        self.predicted_enemy_army_gas = 0

        for unit_count in enemy_composition:
            if unit_count.count > 0:
                # TODO: Overlords!
                if self.unit_values.is_worker(unit_count.enemy_type):
                    self.enemy_known_worker_count += unit_count.count

                mineral_value = self.unit_values.minerals(
                    unit_count.enemy_type) * unit_count.count
                gas_value = self.unit_values.gas(
                    unit_count.enemy_type) * unit_count.count
                minerals_used += mineral_value
                gas_used += gas_value

                if not self.unit_values.is_worker(unit_count.enemy_type) \
                        and self.unit_values.power_by_type(unit_count.enemy_type) > 0.25:
                    self.enemy_power.add_unit(unit_count.enemy_type,
                                              unit_count.count)

                    self.predicted_enemy_composition.append(unit_count)
                    # Save values as to what we know to be true
                    self.enemy_army_known_minerals += mineral_value
                    self.enemy_army_known_gas += gas_value

        mined_minerals: int = 0
        mined_minerals_predict: float = 0
        worker_count_per_base = 12  # TODO: Just random guess

        for zone in self.knowledge.enemy_expansion_zones:
            current_minerals = zone.last_minerals
            if current_minerals is None:
                current_minerals = 0

            last_minerals = self.mineral_dict.get(zone, 0)

            if last_minerals > current_minerals:
                self.mineral_dict[zone] = current_minerals
                self.mineral_updated_dict[zone] = self.ai.time

                if zone.is_enemys:
                    mined_minerals += last_minerals - current_minerals
            elif zone.is_enemys:
                prediction = last_minerals - (
                    self.ai.time - self.mineral_updated_dict.get(zone, 0)
                ) * MINERAL_MINING_SPEED * worker_count_per_base
                prediction = max(0.0, prediction)
                mined_minerals_predict += last_minerals - prediction

        self.enemy_mined_minerals += mined_minerals
        self.enemy_mined_minerals_prediction = round(
            self.enemy_mined_minerals + mined_minerals_predict)

        if gas_miners.exists:

            for miner in gas_miners:  # type: Unit
                last_gas = self.gas_dict.get(miner.position, 2250)
                if miner.is_visible:
                    gas = miner.vespene_contents
                else:
                    gas = max(0.0, last_gas - 169.61 / 60 * INTERVAL)

                self.gas_dict[miner.position] = gas
                self.enemy_mined_gas += last_gas - gas

        lost_tuple: tuple = self.lost_units_manager.calculate_enemy_lost_resources(
        )
        minerals_used += lost_tuple[0]
        gas_used += lost_tuple[1]

        self.predicted_enemy_free_minerals = round(
            self.enemy_base_value_minerals +
            self.enemy_mined_minerals_prediction - minerals_used)

        self.predicted_enemy_free_gas = round(self.enemy_mined_gas - gas_used)

        if self.predicted_enemy_free_minerals < 0:
            # Possibly hidden base or more workers than we think?
            self.print(
                f"Predicting negative free minerals for enemy: {self.predicted_enemy_free_minerals}"
            )

        await self.predict_enemy_composition()

        for unit_count in self.predicted_enemy_composition:
            self.predicted_enemy_power.add_unit(unit_count.enemy_type,
                                                unit_count.count)
            mineral_value = self.unit_values.minerals(
                unit_count.enemy_type) * unit_count.count
            gas_value = self.unit_values.minerals(
                unit_count.enemy_type) * unit_count.count
            self.predicted_enemy_army_minerals += mineral_value
            self.predicted_enemy_army_gas += gas_value

    async def update_own_army_value(self):
        self.own_army_value_minerals = 0
        self.own_army_value_gas = 0

        for unit in self.ai.units:
            if not self.unit_values.is_worker(unit.type_id):
                self.own_army_value_minerals += self.unit_values.minerals(
                    unit.type_id)
                self.own_army_value_gas += self.unit_values.gas(unit.type_id)

    async def predict_enemy_composition(self):
        if self.knowledge.enemy_race == Race.Random:
            return  # let's wait until we know the actual race.

        guesser = CompositionGuesser(self.knowledge)
        guesser.left_minerals = self.predicted_enemy_free_minerals
        guesser.left_gas = self.predicted_enemy_free_gas

        additional_guess: List[UnitCount] = guesser.predict_enemy_composition()
        for unit_count in additional_guess:
            existing = self.find(self.predicted_enemy_composition,
                                 unit_count.enemy_type)
            if existing is None:
                self.predicted_enemy_composition.append(unit_count)
            else:
                existing.count += unit_count.count

    def find(self, lst: List[UnitCount], enemy_type) -> Optional[UnitCount]:
        for unit_count in lst:
            if unit_count.enemy_type == enemy_type:
                return unit_count
        return None

    async def post_update(self):
        await self.debug_message()

    async def debug_message(self):
        if self.knowledge.my_race == Race.Protoss:
            # my_comp = self.enemy_build.gate_type_values(self.predicted_enemy_composition)
            # my_comp.extend(self.enemy_build.robo_type_values(self.predicted_enemy_composition))
            # my_comp.extend(self.enemy_build.star_type_values(self.predicted_enemy_composition))

            enemy_comp = sorted(self.predicted_enemy_composition,
                                key=lambda uc: uc.count,
                                reverse=True)
            # my_comp = sorted(my_comp, key=lambda c: c.count, reverse=True)

            if self.debug:
                client: Client = self.ai._client
                msg = f"Us vs them: {self.own_value} / {self.enemy_value}\n"
                msg += f"Known enemy army (M/G): {self.enemy_army_known_minerals} / {self.enemy_army_known_gas}\n"
                msg += f"Enemy predicted money (M/G): {self.predicted_enemy_free_minerals} / {self.predicted_enemy_free_gas}\n"

                msg += f"\nComposition:\n"

                for unit_count in enemy_comp:
                    msg += f" {unit_count.to_short_string()}"

                # msg += f"\nCounter:\n"

                # for unit_count in my_comp:
                #     if unit_count.count > 0:
                #         msg += f" {unit_count.to_string()}\n"

                client.debug_text_2d(msg, Point2((0.1, 0.1)), None, 16)