Exemple #1
0
    def __init__(self, ai: sc2.BotAI, knowledge: 'Knowledge', x: int, y: int,
                 x2: int, y2: int):
        self.ai = ai
        self.knowledge = knowledge
        self.cache: UnitCacheManager = self.knowledge.unit_cache
        self.bottom_left_x = x
        self.bottom_left_y = y
        self.top_right_x = x2
        self.top_right_y = y2
        self.center = Point2(((x + x2) / 2.0, (y + y2) / 2.0))
        self._x, self._y = self.center.rounded
        self.zone: Optional['Zone'] = None
        self.heat: float = 0
        self.stealth_heat: float = 0
        self.last_enemy_power = ExtendedPower(knowledge.unit_values)

        d2 = 15
        for zone in knowledge.expansion_zones:
            if zone.center_location.distance_to(self.center) < d2:
                if ai.get_terrain_height(
                        zone.center_location) == ai.get_terrain_height(
                            self.center):
                    # if zone == self.knowledge.own_main_zone:
                    #     print("MAIN ZONE")
                    self.zone = zone
    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) 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
Exemple #3
0
    async def start(self, knowledge: "Knowledge"):
        await super().start(knowledge)
        self.cache: UnitCacheManager = self.knowledge.unit_cache
        self.pather: PathingManager = self.knowledge.pathing_manager
        self.tags: List[int] = []

        self.unit_micros: Dict[UnitTypeId, MicroStep] = dict()
        self.all_enemy_power = ExtendedPower(self.unit_values)

        # Micro controllers / handlers
        self.unit_micros[UnitTypeId.DRONE] = MicroWorkers(knowledge)
        self.unit_micros[UnitTypeId.PROBE] = MicroWorkers(knowledge)
        self.unit_micros[UnitTypeId.SCV] = MicroWorkers(knowledge)

        # Protoss
        self.unit_micros[UnitTypeId.ARCHON] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.ADEPT] = MicroAdepts(knowledge)
        self.unit_micros[UnitTypeId.CARRIER] = MicroCarriers(knowledge)
        self.unit_micros[UnitTypeId.COLOSSUS] = MicroColossi(knowledge)
        self.unit_micros[UnitTypeId.DARKTEMPLAR] = MicroZerglings(knowledge)
        self.unit_micros[UnitTypeId.DISRUPTOR] = MicroDisruptor(knowledge)
        self.unit_micros[UnitTypeId.DISRUPTORPHASED] = MicroPurificationNova(
            knowledge)
        self.unit_micros[UnitTypeId.HIGHTEMPLAR] = MicroHighTemplars(knowledge)
        self.unit_micros[UnitTypeId.OBSERVER] = MicroObservers(knowledge)
        self.unit_micros[UnitTypeId.ORACLE] = MicroOracles(knowledge)
        self.unit_micros[UnitTypeId.PHOENIX] = MicroPhoenixes(knowledge)
        self.unit_micros[UnitTypeId.SENTRY] = MicroSentries(knowledge)
        self.unit_micros[UnitTypeId.STALKER] = MicroStalkers(knowledge)
        self.unit_micros[UnitTypeId.WARPPRISM] = MicroWarpPrism(knowledge)
        self.unit_micros[UnitTypeId.VOIDRAY] = MicroVoidrays(knowledge)
        self.unit_micros[UnitTypeId.ZEALOT] = MicroZealots(knowledge)

        # Zerg
        self.unit_micros[UnitTypeId.ZERGLING] = MicroZerglings(knowledge)
        self.unit_micros[UnitTypeId.ULTRALISK] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.OVERSEER] = MicroOverseers(knowledge)
        self.unit_micros[UnitTypeId.QUEEN] = MicroQueens(knowledge)
        self.unit_micros[UnitTypeId.RAVAGER] = MicroRavagers(knowledge)

        self.unit_micros[UnitTypeId.LURKERMP] = MicroLurkers(knowledge)
        self.unit_micros[UnitTypeId.INFESTOR] = MicroInfestors(knowledge)
        self.unit_micros[UnitTypeId.SWARMHOSTMP] = MicroSwarmHosts(knowledge)
        self.unit_micros[UnitTypeId.LOCUSTMP] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.LOCUSTMPFLYING] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.VIPER] = MicroVipers(knowledge)

        # Terran
        self.unit_micros[UnitTypeId.HELLIONTANK] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.SIEGETANK] = MicroTanks(knowledge)
        self.unit_micros[UnitTypeId.VIKINGFIGHTER] = MicroVikings(knowledge)
        self.unit_micros[UnitTypeId.MARINE] = MicroBio(knowledge)
        self.unit_micros[UnitTypeId.MARAUDER] = MicroBio(knowledge)
        self.unit_micros[UnitTypeId.BATTLECRUISER] = MicroBattleCruisers(
            knowledge)
        self.unit_micros[UnitTypeId.RAVEN] = MicroRavens(knowledge)
        self.unit_micros[UnitTypeId.MEDIVAC] = MicroMedivacs(knowledge)

        self.generic_micro = GenericMicro(knowledge)
        self.regroup_threshold = 0.75
 async def start(self, knowledge: "Knowledge"):
     await super().start(knowledge)
     self.enemy_predicter: EnemyArmyPredicter = knowledge.enemy_army_predicter
     self.our_power = ExtendedPower(self.unit_values)
     self.enemy_power: ExtendedPower = ExtendedPower(self.unit_values)
     self.enemy_predict_power: ExtendedPower = ExtendedPower(self.unit_values)
     self.resource_updater = IntervalFunc(self.ai, self.save_resources_status, 1)
     self.resource_updater.execute()
    async def start(self, knowledge: "Knowledge"):
        await super().start(knowledge)
        self.cache: UnitCacheManager = self.knowledge.unit_cache
        self.pather: PathingManager = self.knowledge.pathing_manager
        self.tags: List[int] = []
        self.all_enemy_power = ExtendedPower(self.unit_values)

        await self.default_rules.start(knowledge)
Exemple #6
0
    def calc_needs_evacuation(self):
        """
        Checks if the zone needs evacuation for the workers mining there.
        This is a method because it is quite CPU heavy.
        """

        enemies: Units = self.cache.enemy_in_range(self.mineral_line_center, 10)
        power = ExtendedPower(self.unit_values)
        power.add_units(enemies)
        if power.ground_power > 3 and enemies.exclude_type(self.unit_values.worker_types):
            self.needs_evacuation = True
        else:
            self.needs_evacuation = False
    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_values)
        for type_id in self._known_enemy_units_dict:
            if self.unit_values.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 #8
0
    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)
Exemple #9
0
class HeatArea:
    def __init__(self, ai: sc2.BotAI, knowledge: 'Knowledge', x: int, y: int,
                 x2: int, y2: int):
        self.ai = ai
        self.knowledge = knowledge
        self.cache: UnitCacheManager = self.knowledge.unit_cache
        self.bottom_left_x = x
        self.bottom_left_y = y
        self.top_right_x = x2
        self.top_right_y = y2
        self.center = Point2(((x + x2) / 2.0, (y + y2) / 2.0))
        self._x, self._y = self.center.rounded
        self.zone: Optional['Zone'] = None
        self.heat: float = 0
        self.stealth_heat: float = 0
        self.last_enemy_power = ExtendedPower(knowledge.unit_values)

        d2 = 15
        for zone in knowledge.expansion_zones:
            if zone.center_location.distance_to(self.center) < d2:
                if ai.get_terrain_height(
                        zone.center_location) == ai.get_terrain_height(
                            self.center):
                    # if zone == self.knowledge.own_main_zone:
                    #     print("MAIN ZONE")
                    self.zone = zone

    def update(self, time_change: float):
        if self.stealth_heat > 0:
            self.stealth_heat = min(
                2,
                max(0, (self.stealth_heat - time_change) *
                    (1 - time_change * 0.5)))

        if self.heat > 0:
            if self.is_visible():
                self.heat = max(0, (self.heat - time_change * 0.02) *
                                (1 - time_change * 0.5))
            else:
                self.heat = max(0, (self.heat - time_change * 0.01) *
                                (1 - time_change * 0.25))

        self.heat += self.last_enemy_power.power * time_change
        self.last_enemy_power.clear()

    def is_visible(self) -> bool:
        # TODO: Check all corners?
        return self.ai.state.visibility.data_numpy[self._y, self._x] == 2
Exemple #10
0
    def _should_attack(self, power: ExtendedPower) -> bool:
        if self.attack_on_advantage:
            if ((self.game_analyzer.our_army_predict
                 in at_least_clear_advantage
                 and self.game_analyzer.our_income_advantage
                 in at_least_small_disadvantage)
                    or (self.game_analyzer.our_army_predict
                        in at_least_small_advantage
                        and self.game_analyzer.our_income_advantage
                        in at_least_clear_disadvantage)):
                # Our army is bigger but economy is weaker, attack!
                return True

            if ((self.game_analyzer.our_army_predict
                 in at_least_small_disadvantage
                 and self.game_analyzer.our_income_advantage
                 in at_least_clear_advantage)
                    or (self.game_analyzer.our_army_predict
                        in at_least_clear_disadvantage
                        and self.game_analyzer.our_income_advantage
                        in at_least_small_advantage)):
                # Our army is smaller but economy is better, focus on defence!
                return False

        enemy_total_power: ExtendedPower = self.knowledge.enemy_units_manager.enemy_total_power
        enemy_total_power.multiply(ENEMY_TOTAL_POWER_MULTIPLIER)
        multiplier = ENEMY_TOTAL_POWER_MULTIPLIER

        zone_count = 0
        for zone in self.knowledge.expansion_zones:  # type: Zone
            if zone.is_enemys:
                zone_count += 1

        enemy_main: Zone = self.knowledge.expansion_zones[-1]
        enemy_natural: Zone = self.knowledge.expansion_zones[-2]

        if zone_count == 1 and enemy_main.is_enemys:
            # We should seriously consider whether we want to crash and burn against a one base defense
            enemy_total_power.add_units(enemy_main.enemy_static_defenses)
            #multiplier *= 2

        elif zone_count == 2 and enemy_natural.is_enemys:
            enemy_total_power.add_units(enemy_natural.enemy_static_defenses)

            # if (self.knowledge.enemy_race == Race.Terran
            #         and self.knowledge.enemy_units_manager.unit_count(UnitTypeId.SIEGETANK) > 1):
            #     multiplier = 1.6

        enemy_total_power.power = max(self.start_attack_power,
                                      enemy_total_power.power)

        if power.is_enough_for(enemy_total_power, 1 / multiplier):
            self.print(
                f"Power {power.power:.2f} is larger than required attack power {enemy_total_power.power:.2f} -> attack!"
            )
            return True
        if self.ai.supply_used > 190:
            self.print(f"Supply is {self.ai.supply_used} -> attack!")
            return True
        return False
    def get_defenders(self, power: ExtendedPower, position: Point2) -> Units:
        units = Units([], self.ai)
        current_power = ExtendedPower(self.unit_values)

        self._defenders_from(UnitTask.Idle, current_power, position, power, units)
        self._defenders_from(UnitTask.Moving, current_power, position, power, units)
        self._defenders_from(UnitTask.Fighting, current_power, position, power, units)
        self._defenders_from(UnitTask.Attacking, current_power, position, power, units)
        return units
    def calc_total_power(self, units: Units) -> ExtendedPower:
        """Calculates total power for the given units (either own or enemy)."""

        total_power = ExtendedPower(self)

        if not units.exists:
            return total_power

        first_owner_id = None

        for unit in units:
            if first_owner_id and unit.owner_id and not unit.owner_id == first_owner_id:
                logging.warning(
                    f"Unit owner id does not match. tag: {unit.tag} type: {unit.type_id} "
                    + f"owner id: {unit.type_id} (expected {first_owner_id}")
                continue
            if unit.can_be_attacked:
                first_owner_id = unit.owner_id
            total_power.add_unit(unit)
        return total_power
    def __init__(self, units: Units, knowledge: "Knowledge"):
        self.knowledge = knowledge
        self.unit_values = knowledge.unit_values
        self.units = units
        self.center: Point2 = sc2math.unit_geometric_median(units)
        self.ground_units = self.units.not_flying
        if self.ground_units:
            self.center: Point2 = self.ground_units.closest_to(
                (self.center)).position

        self.power = ExtendedPower(self.unit_values)
        self.power.add_units(self.units)
        self.debug_index = 0
        self._total_distance: Optional[float] = None
        self._area_by_circles: float = 0
        self.average_speed = 0

        for unit in self.units:
            self.average_speed += knowledge.unit_values.real_speed(unit)

        if len(self.units) > 1:
            self.average_speed /= len(self.units)
Exemple #14
0
    def __init__(self, knowledge):
        self.knowledge: "Knowledge" = knowledge
        self.ai: sc2.BotAI = knowledge.ai
        self.unit_values: UnitValue = knowledge.unit_values
        self.cd_manager: CooldownManager = knowledge.cooldown_manager
        self.pather: PathingManager = knowledge.pathing_manager
        self.cache: UnitCacheManager = knowledge.unit_cache
        self.delay_to_shoot = self.ai._client.game_step + 1.5
        self.enemy_groups: List[CombatUnits] = []
        self.ready_to_attack_ratio: float = 0.0
        self.center: Point2 = Point2((0, 0))
        self.group: CombatUnits
        self.engage_ratio = 0
        self.can_engage_ratio = 0
        self.closest_group: CombatUnits
        self.engaged: Dict[int, List[int]] = dict()
        self.engaged_power = ExtendedPower(knowledge.unit_values)
        self.our_power = ExtendedPower(knowledge.unit_values)
        self.closest_units: Dict[int, Optional[Unit]] = dict()
        self.move_type = MoveType.Assault
        self.attack_range = 0
        self.enemy_attack_range = 0

        self.focus_fired: Dict[int, float] = dict()
class GameAnalyzer(ManagerBase):
    def __init__(self):
        super().__init__()
        self._enemy_air_percentage = 0
        self._our_income_advantage = 0
        self._our_predicted_army_advantage = 0
        self._our_predicted_tech_advantage = 0
        self.enemy_gas_income = 0
        self.enemy_mineral_income = 0
        self.our_zones = 0
        self.enemy_zones = 0
        self.our_power: ExtendedPower = None
        self.enemy_power: ExtendedPower = None
        self.enemy_predict_power: ExtendedPower = None
        self.predicted_defeat_time = 0.0
        self.minerals_left: List[int] = []
        self.vespene_left: List[int] = []
        self.resource_updater: IntervalFunc = None

        self._last_income: Advantage = Advantage.Even
        self._last_army: Advantage = Advantage.Even
        self._last_predict: Advantage = Advantage.Even

    async def start(self, knowledge: "Knowledge"):
        await super().start(knowledge)
        self.enemy_predicter: EnemyArmyPredicter = knowledge.enemy_army_predicter
        self.our_power = ExtendedPower(self.unit_values)
        self.enemy_power: ExtendedPower = ExtendedPower(self.unit_values)
        self.enemy_predict_power: ExtendedPower = ExtendedPower(self.unit_values)
        self.resource_updater = IntervalFunc(self.ai, self.save_resources_status, 1)
        self.resource_updater.execute()

    def save_resources_status(self):
        self.minerals_left.append(self.ai.minerals)
        self.vespene_left.append(self.ai.vespene)

    async def update(self):
        self.resource_updater.execute()

        self.our_power.clear()
        self.our_zones = 0
        self.enemy_zones = 0
        our_income = self.knowledge.income_calculator.mineral_income + self.knowledge.income_calculator.gas_income

        if self.knowledge.my_worker_type is None:
            # random and we haven't seen enemy race yat
            enemy_workers = 12
        else:
            enemy_workers = self.knowledge.enemy_units_manager.enemy_worker_count
            if not self.knowledge.enemy_main_zone.is_scouted_at_least_once:
                enemy_workers += 12

        mineral_fields = 0
        for zone in self.knowledge.zone_manager.expansion_zones:  # type: Zone
            if zone.is_enemys:
                self.enemy_zones += 1
                mineral_fields += len(zone.mineral_fields)
            if zone.is_ours:
                self.our_zones += 1

        built_vespene = len(self.cache.enemy(self.unit_values.gas_miners))
        self._enemy_gas_income = min(enemy_workers, built_vespene * 3) * GAS_MINE_RATE
        workers_on_minerals = min(mineral_fields * 2, enemy_workers - built_vespene * 3)
        workers_on_minerals = max(0, workers_on_minerals)
        self.enemy_mineral_income = workers_on_minerals

        enemy_income = self.enemy_mineral_income + self._enemy_gas_income
        self._our_income_advantage = our_income - enemy_income
        self.our_power.add_units(
            self.ai.units.filter(lambda u: u.is_ready and u.type_id != self.knowledge.my_worker_type)
        )

        self.enemy_predict_power = self.enemy_predicter.predicted_enemy_power
        self.enemy_power = self.enemy_predicter.enemy_power

        self._enemy_air_percentage = 0
        if self.enemy_predict_power.air_presence > 0:
            self._enemy_air_percentage = self.enemy_predict_power.air_power / self.enemy_predict_power.power

        being_defeated = self.predicting_defeat
        if being_defeated and self.predicted_defeat_time == 0.0:
            self.predicted_defeat_time = self.ai.time
        elif not being_defeated and self.predicted_defeat_time != 0.0:
            self.predicted_defeat_time = 0

        income = self._calc_our_income_advantage()
        army = self._calc_our_army_advantage()
        predict = self._calc_our_army_predict()

        if self._last_income != income:
            self.print(f"Income advantage is now {income.name}")

        if self._last_army != army:
            self.print(f"Known army advantage is now {army.name}")

        if self._last_predict != predict:
            self.print(f"Predicted army advantage is now {predict.name}")

        self._last_income = income
        self._last_army = army
        self._last_predict = predict

    async def post_update(self):
        if self.debug:
            msg = f"Our income: {self.knowledge.income_calculator.mineral_income} / {round(self.knowledge.income_calculator.gas_income)}"
            msg += f"\nEnemy income: {self.enemy_mineral_income} / {round(self.enemy_gas_income)}"
            msg += (
                f"\nResources: {round(self._our_income_advantage)}+{self.our_zones - self.enemy_zones}"
                f" ({self.our_income_advantage.name})"
            )
            msg += (
                f"\nArmy: {round(self.our_power.power)} vs"
                f" {round(self.enemy_power.power)} ({self.our_army_advantage.name})"
            )
            msg += (
                f"\nArmy predict: {round(self.our_power.power)} vs"
                f" {round(self.enemy_predict_power.power)} ({self.our_army_predict.name})"
            )
            msg += f"\nEnemy air: {self.enemy_air.name}"
            self.client.debug_text_2d(msg, Point2((0.4, 0.15)), None, 14)

    @property
    def our_income_advantage(self) -> Advantage:
        return self._last_income

    def _calc_our_income_advantage(self) -> Advantage:
        number = self._our_income_advantage + (self.our_zones - self.enemy_zones) * 10

        if number > 40:
            return Advantage.OverwhelmingAdvantage
        if number < -40:
            return Advantage.OverwhelmingDisadvantage

        if number > 20:
            return Advantage.ClearAdvantage
        if number < -20:
            return Advantage.ClearDisadvantage

        if number > 10:
            return Advantage.SmallAdvantage
        if number < -10:
            return Advantage.SmallDisadvantage

        if number > 5:
            return Advantage.SlightAdvantage
        if number < -5:
            return Advantage.SlightDisadvantage

        return Advantage.Even

    @property
    def army_at_least_clear_disadvantage(self) -> bool:
        return self.our_army_predict in at_least_clear_disadvantage

    @property
    def army_at_least_small_disadvantage(self) -> bool:
        return self.our_army_predict in at_least_small_disadvantage

    @property
    def army_at_least_clear_advantage(self) -> bool:
        return self.our_army_predict in at_least_clear_advantage

    @property
    def army_at_least_small_advantage(self) -> bool:
        return self.our_army_predict in at_least_small_advantage

    @property
    def army_at_least_advantage(self) -> bool:
        return self.our_army_predict in at_least_advantage

    @property
    def army_can_survive(self) -> bool:
        return self.our_army_predict not in at_least_small_disadvantage

    @property
    def predicting_victory(self) -> bool:
        return (
            self.our_army_predict == Advantage.OverwhelmingAdvantage
            and self.our_income_advantage == Advantage.OverwhelmingAdvantage
        )

    @property
    def been_predicting_defeat_for(self) -> float:
        if self.predicted_defeat_time == 0:
            return 0
        return self.ai.time - self.predicted_defeat_time

    @property
    def predicting_defeat(self) -> bool:
        return self.our_army_predict == Advantage.OverwhelmingDisadvantage and (
            self.ai.supply_workers < 5 or self.our_income_advantage == Advantage.OverwhelmingDisadvantage
        )

    @property
    def our_army_predict(self) -> Advantage:
        return self._last_predict

    def _calc_our_army_predict(self) -> Advantage:
        if self.our_power.is_enough_for(self.enemy_predict_power, our_percentage=1 / 1.1):
            if self.our_power.power > 20 and self.our_power.is_enough_for(
                self.enemy_predict_power, our_percentage=1 / 3
            ):
                return Advantage.OverwhelmingAdvantage
            if self.our_power.power > 10 and self.our_power.is_enough_for(
                self.enemy_predict_power, our_percentage=1 / 2
            ):
                return Advantage.ClearAdvantage
            if self.our_power.power > 5 and self.our_power.is_enough_for(
                self.enemy_predict_power, our_percentage=1 / 1.4
            ):
                return Advantage.SmallAdvantage
            return Advantage.SlightAdvantage

        if self.enemy_predict_power.is_enough_for(self.our_power, our_percentage=1 / 1.1):
            if self.enemy_predict_power.power > 20 and self.enemy_predict_power.is_enough_for(
                self.our_power, our_percentage=1 / 3
            ):
                return Advantage.OverwhelmingDisadvantage
            if self.enemy_predict_power.power > 10 and self.enemy_predict_power.is_enough_for(
                self.our_power, our_percentage=1 / 2
            ):
                return Advantage.ClearDisadvantage
            if self.enemy_predict_power.power > 5 and self.enemy_predict_power.is_enough_for(
                self.our_power, our_percentage=1 / 1.4
            ):
                return Advantage.SmallDisadvantage
            return Advantage.SlightDisadvantage
        return Advantage.Even

    @property
    def our_army_advantage(self) -> Advantage:
        return self._last_army

    def _calc_our_army_advantage(self) -> Advantage:
        if self.our_power.is_enough_for(self.enemy_power, our_percentage=1 / 1.1):
            if self.our_power.power > 20 and self.our_power.is_enough_for(self.enemy_power, our_percentage=1 / 3):
                return Advantage.OverwhelmingAdvantage
            if self.our_power.power > 10 and self.our_power.is_enough_for(self.enemy_power, our_percentage=1 / 2):
                return Advantage.ClearAdvantage
            if self.our_power.power > 5 and self.our_power.is_enough_for(self.enemy_power, our_percentage=1 / 1.4):
                return Advantage.SmallAdvantage
            return Advantage.SlightAdvantage

        if self.enemy_power.is_enough_for(self.our_power, our_percentage=1 / 1.1):
            if self.enemy_power.power > 20 and self.enemy_power.is_enough_for(self.our_power, our_percentage=1 / 3):
                return Advantage.OverwhelmingDisadvantage
            if self.enemy_power.power > 10 and self.enemy_power.is_enough_for(self.our_power, our_percentage=1 / 2):
                return Advantage.ClearDisadvantage
            if self.enemy_power.power > 5 and self.enemy_power.is_enough_for(self.our_power, our_percentage=1 / 1.4):
                return Advantage.SmallDisadvantage
            return Advantage.SlightDisadvantage
        return Advantage.Even

    @property
    def enemy_air(self) -> AirArmy:
        if self._enemy_air_percentage > 0.90:
            return AirArmy.AllAir
        if self._enemy_air_percentage > 0.65:
            return AirArmy.AlmostAllAir
        if self._enemy_air_percentage > 0.35:
            return AirArmy.Mixed
        if self._enemy_air_percentage > 0:
            return AirArmy.SomeAir
        return AirArmy.NoAir

    async def on_end(self, game_result: Result):
        own_types: List[UnitTypeId] = []
        own_types_left: Dict[UnitTypeId, int] = {}
        enemy_types: List[UnitTypeId] = []
        enemy_types_left: Dict[UnitTypeId, int] = {}

        lost_data = self.knowledge.lost_units_manager.get_own_enemy_lost_units()
        own_lost: Dict[UnitTypeId, List[Unit]] = lost_data[0]
        enemy_lost: Dict[UnitTypeId, List[Unit]] = lost_data[1]

        for unit_type, units in self.cache.own_unit_cache.items():  # type: (UnitTypeId, Units)
            type_id = self.unit_values.real_type(unit_type)
            if type_id not in own_types:
                own_types.append(type_id)
            val = own_types_left.get(type_id, 0)
            own_types_left[type_id] = val + units.amount

        for unit_count in self.knowledge.enemy_units_manager.enemy_composition:  # type: UnitCount
            unit_type = unit_count.enemy_type
            if unit_type not in enemy_types:
                enemy_types.append(unit_type)
            val = enemy_types_left.get(unit_type, 0)
            enemy_types_left[unit_type] = val + unit_count.count

        for unit_type, units in own_lost.items():  # type: (UnitTypeId, List[Unit])
            if unit_type not in own_types:
                own_types.append(unit_type)

        for unit_type, units in enemy_lost.items():  # type: (UnitTypeId, List[Unit])
            if unit_type not in enemy_types:
                enemy_types.append(unit_type)

        self.print_end("Own units:")
        self._print_by_type(own_types, own_lost, own_types_left)
        self.print_end("Enemy units:")
        self._print_by_type(enemy_types, enemy_lost, enemy_types_left)

        maxed_minerals = max(self.minerals_left)
        avg_minerals = sum(self.minerals_left) / len(self.minerals_left)

        maxed_gas = max(self.vespene_left)
        avg_gas = sum(self.vespene_left) / len(self.vespene_left)
        self.print_end(f"Minerals max {maxed_minerals} Average {round(avg_minerals)}")
        self.print_end(f"Vespene max {maxed_gas} Average {round(avg_gas)}")

    def _print_by_type(
        self, types: List[UnitTypeId], lost_units: Dict[UnitTypeId, List[Unit]], left_units: Dict[UnitTypeId, int]
    ):
        def get_counts(unit_type: UnitTypeId) -> tuple:
            dead = len(lost_units.get(unit_type, []))
            alive = left_units.get(unit_type, 0)
            total = dead + alive
            return total, alive, dead

        # Sort types by total count
        types = sorted(types, key=lambda t: get_counts(t)[0], reverse=True)

        for unit_type in types:
            counts = get_counts(unit_type)
            self.print_end(
                f"{str(unit_type.name).ljust(17)} "
                f"total: {str(counts[0]).rjust(3)} "
                f"alive: {str(counts[1]).rjust(3)} "
                f"dead: {str(counts[2]).rjust(3)} "
            )

    def print_end(self, msg: str):
        self.knowledge.print(msg, "GameAnalyzerEnd", stats=False)
Exemple #16
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 #17
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
Exemple #18
0
 def enemy_static_power(self) -> ExtendedPower:
     """Returns power of enemy static defenses."""
     power = ExtendedPower(self.unit_values)
     power.add_units(self.enemy_static_defenses)
     return power
Exemple #19
0
class MicroStep(ABC, Component):
    engaged_power: ExtendedPower
    our_power: ExtendedPower
    delay_to_shoot: float
    enemies_near_by: Units
    closest_group: CombatUnits
    closest_group_distance: float

    def __init__(self):
        self.enemy_groups: List[CombatUnits] = []
        self.ready_to_attack_ratio: float = 0.0
        self.center: Point2 = Point2((0, 0))
        self.group: CombatUnits
        self.engage_ratio = 0
        self.can_engage_ratio = 0
        self.closest_group: CombatUnits
        self.engaged: Dict[int, List[int]] = dict()

        self.closest_units: Dict[int, Optional[Unit]] = dict()
        self.move_type = MoveType.Assault
        self.attack_range = 0
        self.enemy_attack_range = 0

        self.focus_fired: Dict[int, float] = dict()

    async def start(self, knowledge: "Knowledge"):
        await super().start(knowledge)
        self.engaged_power = ExtendedPower(knowledge.unit_values)
        self.our_power = ExtendedPower(knowledge.unit_values)

    def init_group(
        self,
        rules: "MicroRules",
        group: CombatUnits,
        units: Units,
        enemy_groups: List[CombatUnits],
        move_type: MoveType,
    ):
        self.rules = rules

        self.focus_fired.clear()
        self.group = group
        self.move_type = move_type

        self.our_power = group.power
        self.closest_units.clear()
        self.engaged_power.clear()

        self.rules.init_group_func(self, group, units, enemy_groups, move_type)

    def ready_to_shoot(self, unit: Unit) -> bool:
        return self.rules.ready_to_shoot_func(self, unit)

    def group_solve_combat(self, units: Units,
                           current_command: Action) -> Action:
        return self.rules.group_solve_combat_func(self, units, current_command)

    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        return self.rules.unit_solve_combat_func(self, unit, current_command)

    def focus_fire(self, unit: Unit, current_command: Action,
                   prio: Optional[Dict[UnitTypeId, int]]) -> Action:
        return self.rules.focus_fire_func(self, unit, current_command, prio)

    def melee_focus_fire(
            self,
            unit: Unit,
            current_command: Action,
            prio: Optional[Dict[UnitTypeId, int]] = None) -> Action:
        return self.rules.melee_focus_fire_func(self, unit, current_command,
                                                prio)

    def last_targeted(self, unit: Unit) -> Optional[int]:
        if unit.orders:
            # action: UnitCommand
            # current_action: UnitOrder
            current_action = unit.orders[0]
            # targeting unit
            if isinstance(current_action.target, int):
                # tag found
                return current_action.target
        return None

    def is_locked_on(self, unit: Unit) -> bool:
        if unit.has_buff(BuffId.LOCKON):
            return True
        return False

    def is_target(self, unit: Unit) -> bool:
        return not unit.is_memory and unit.can_be_attacked and not unit.is_hallucination and not unit.is_snapshot
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        prism = unit
        warpgates: Units = self.knowledge.unit_cache.own(UnitTypeId.WARPGATE)

        count = len(warpgates)
        ready = 0
        for gate in warpgates:  # type: Unit
            if gate.is_transforming or self.cd_manager.is_ready(
                    gate.tag, AbilityId.WARPGATETRAIN_ZEALOT):
                ready += 1

        if (ready > 2 and ready >= count
                and unit.type_id == UnitTypeId.WARPPRISM
                and self.ai.supply_left > 3 and self.cd_manager.is_ready(
                    prism.tag, AbilityId.MORPH_WARPPRISMTRANSPORTMODE, 6)):
            # TODO: Is it safe to warp in?
            self.cd_manager.used_ability(prism.tag,
                                         AbilityId.MORPH_WARPPRISMPHASINGMODE)
            return Action(None, False, AbilityId.MORPH_WARPPRISMPHASINGMODE)

        elif unit.type_id == UnitTypeId.WARPPRISMPHASING:
            not_ready = self.knowledge.unit_cache.own(
                self.unit_values.gate_types).not_ready

            if self.cd_manager.is_ready(
                    prism.tag, AbilityId.MORPH_WARPPRISMPHASINGMODE,
                    2.5) and (len(not_ready) < 1
                              or not_ready.closest_distance_to(prism) > 4):

                self.cd_manager.used_ability(
                    prism.tag, AbilityId.MORPH_WARPPRISMTRANSPORTMODE)
                return Action(None, False,
                              AbilityId.MORPH_WARPPRISMTRANSPORTMODE)

        if prism.cargo_used:
            for passenger in prism.passengers:  # type: Unit
                if self.release_tags.get(passenger.tag, 0) < self.ai.time:
                    if not self.ai.in_pathing_grid(prism):
                        break

                    stop_drop = False

                    for enemy in self.knowledge.unit_cache.enemy_in_range(
                            prism.position, 4):  # type: Unit
                        if enemy.radius + 1 > prism.distance_to(enemy):
                            stop_drop = True
                            break

                    if stop_drop:
                        break

                    # return CombatAction(prism, passenger, False, AbilityId.UNLOADALLAT_WARPPRISM)
                    return Action(prism, False,
                                  AbilityId.UNLOADALLAT_WARPPRISM)

        power = ExtendedPower(self.unit_values)
        power.add_units(
            self.knowledge.unit_cache.enemy_in_range(prism.position, 12))

        if power.power < 3:

            return self.find_safe_position(current_command)

        if prism.cargo_left and prism.shield > 0 and prism.shield + prism.health > 50:
            best_score = 0
            best_unit = None
            for own_unit in self.group.units:  # type: Unit
                if own_unit.cargo_size > prism.cargo_left:
                    continue
                if own_unit.shield:
                    continue
                if own_unit.weapon_cooldown < 2:
                    continue
                if own_unit.distance_to(prism) > 12:
                    continue

                score = (self.unit_values.ground_range(own_unit) *
                         (1.1 - own_unit.health_percentage) *
                         self.unit_values.power(own_unit) - 1)

                if score > best_score:
                    best_score = score
                    best_unit = own_unit

            if best_unit is not None:
                self.release_tags[
                    best_unit.
                    tag] = self.ai.time + best_unit.weapon_cooldown / 22.4
                return Action(best_unit, False, AbilityId.SMART)

        return self.find_safe_position(current_command)
class GroupCombatManager(ManagerBase):
    def __init__(self):
        # How much distance must be between units to consider them to be in different groups
        self.own_group_threshold = 7
        super().__init__()

    async def start(self, knowledge: "Knowledge"):
        await super().start(knowledge)
        self.cache: UnitCacheManager = self.knowledge.unit_cache
        self.pather: PathingManager = self.knowledge.pathing_manager
        self.tags: List[int] = []

        self.unit_micros: Dict[UnitTypeId, MicroStep] = dict()
        self.all_enemy_power = ExtendedPower(self.unit_values)

        # Micro controllers / handlers
        self.unit_micros[UnitTypeId.DRONE] = MicroWorkers(knowledge)
        self.unit_micros[UnitTypeId.PROBE] = MicroWorkers(knowledge)
        self.unit_micros[UnitTypeId.SCV] = MicroWorkers(knowledge)

        # Protoss
        self.unit_micros[UnitTypeId.ARCHON] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.ADEPT] = MicroAdepts(knowledge)
        self.unit_micros[UnitTypeId.CARRIER] = MicroCarriers(knowledge)
        self.unit_micros[UnitTypeId.COLOSSUS] = MicroColossi(knowledge)
        self.unit_micros[UnitTypeId.DARKTEMPLAR] = MicroZerglings(knowledge)
        self.unit_micros[UnitTypeId.DISRUPTOR] = MicroDisruptor(knowledge)
        self.unit_micros[UnitTypeId.DISRUPTORPHASED] = MicroPurificationNova(knowledge)
        self.unit_micros[UnitTypeId.HIGHTEMPLAR] = MicroHighTemplars(knowledge)
        self.unit_micros[UnitTypeId.OBSERVER] = MicroObservers(knowledge)
        self.unit_micros[UnitTypeId.ORACLE] = MicroOracles(knowledge)
        self.unit_micros[UnitTypeId.PHOENIX] = MicroPhoenixes(knowledge)
        self.unit_micros[UnitTypeId.SENTRY] = MicroSentries(knowledge)
        self.unit_micros[UnitTypeId.STALKER] = MicroStalkers(knowledge)
        self.unit_micros[UnitTypeId.WARPPRISM] = MicroWarpPrism(knowledge)
        self.unit_micros[UnitTypeId.VOIDRAY] = MicroVoidrays(knowledge)
        self.unit_micros[UnitTypeId.ZEALOT] = MicroZealots(knowledge)

        # Zerg
        self.unit_micros[UnitTypeId.ZERGLING] = MicroZerglings(knowledge)
        self.unit_micros[UnitTypeId.ULTRALISK] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.OVERSEER] = MicroOverseers(knowledge)
        self.unit_micros[UnitTypeId.QUEEN] = MicroQueens(knowledge)
        self.unit_micros[UnitTypeId.RAVAGER] = MicroRavagers(knowledge)
        self.unit_micros[UnitTypeId.ROACH] = MicroRoaches(knowledge)
        self.unit_micros[UnitTypeId.LURKERMP] = MicroLurkers(knowledge)
        self.unit_micros[UnitTypeId.INFESTOR] = MicroInfestors(knowledge)
        self.unit_micros[UnitTypeId.SWARMHOSTMP] = MicroSwarmHosts(knowledge)
        self.unit_micros[UnitTypeId.LOCUSTMP] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.LOCUSTMPFLYING] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.VIPER] = MicroVipers(knowledge)

        # Terran
        self.unit_micros[UnitTypeId.HELLIONTANK] = NoMicro(knowledge)
        self.unit_micros[UnitTypeId.SIEGETANK] = MicroTanks(knowledge)
        self.unit_micros[UnitTypeId.VIKINGFIGHTER] = MicroVikings(knowledge)
        self.unit_micros[UnitTypeId.MARINE] = MicroBio(knowledge)
        self.unit_micros[UnitTypeId.MARAUDER] = MicroBio(knowledge)
        self.unit_micros[UnitTypeId.BATTLECRUISER] = MicroBattleCruisers(knowledge)
        self.unit_micros[UnitTypeId.RAVEN] = MicroRavens(knowledge)
        self.unit_micros[UnitTypeId.MEDIVAC] = MicroMedivacs(knowledge)

        self.generic_micro = GenericMicro(knowledge)
        self.regroup_threshold = 0.75

    async def update(self):
        self.enemy_groups: List[CombatUnits] = self.group_enemy_units()
        self.all_enemy_power.clear()

        for group in self.enemy_groups:  # type: CombatUnits
            self.all_enemy_power.add_units(group.units)

    async def post_update(self):
        pass

    @property
    def debug(self):
        return self._debug and self.knowledge.debug

    def add_unit(self, unit: Unit):
        if unit.type_id in ignored:  # Just no
            return

        self.tags.append(unit.tag)

    def add_units(self, units: Units):
        for unit in units:
            self.add_unit(unit)

    def get_all_units(self) -> Units:
        units = Units([], self.ai)
        for tag in self.tags:
            unit = self.cache.by_tag(tag)
            if unit:
                units.append(unit)
        return units

    def execute(self, target: Point2, move_type=MoveType.Assault):
        our_units = self.get_all_units()
        if len(our_units) < 1:
            return

        self.own_groups: List[CombatUnits] = self.group_own_units(our_units)

        total_power = ExtendedPower(self.unit_values)

        for group in self.own_groups:
            total_power.add_power(group.power)

        if self.debug:
            fn = lambda group: group.center.distance_to(self.ai.start_location)
            sorted_list = sorted(self.own_groups, key=fn)
            for i in range(0, len(sorted_list)):
                sorted_list[i].debug_index = i

        for group in self.own_groups:
            center = group.center
            closest_enemies = group.closest_target_group(self.enemy_groups)
            own_closest_group = self.closest_group(center, self.own_groups)

            if closest_enemies is None:
                if move_type == MoveType.PanicRetreat:
                    self.move_to(group, target, move_type)
                else:
                    self.attack_to(group, target, move_type)
            else:
                power = group.power
                enemy_power = ExtendedPower(closest_enemies)

                is_in_combat = group.is_in_combat(closest_enemies)

                if move_type == MoveType.DefensiveRetreat or move_type == MoveType.PanicRetreat:
                    self.move_to(group, target, move_type)
                    break

                if power.power > self.regroup_threshold * total_power.power:
                    # Most of the army is here
                    if group.is_too_spread_out() and not is_in_combat:
                        self.regroup(group, group.center)
                    else:
                        self.attack_to(group, target, move_type)

                elif is_in_combat:
                    if not power.is_enough_for(enemy_power, 0.75):
                        # Regroup if possible

                        if own_closest_group:
                            self.move_to(group, own_closest_group.center, MoveType.ReGroup)
                        else:
                            # fight to bitter end
                            self.attack_to(group, closest_enemies.center, move_type)
                    else:
                        self.attack_to(group, closest_enemies.center, move_type)
                else:
                    # if self.faster_group_should_regroup(group, own_closest_group):
                    #     self.move_to(group, own_closest_group.center, MoveType.ReGroup)

                    if group.power.is_enough_for(self.all_enemy_power, 0.85):
                        # We have enough units here to crush everything the enemy has
                        self.attack_to(group, closest_enemies.center, move_type)
                    else:
                        # Regroup if possible
                        if move_type == MoveType.Assault:
                            # Group towards attack target
                            own_closest_group = self.closest_group(target, self.own_groups)
                        else:
                            # Group up with closest group
                            own_closest_group = self.closest_group(center, self.own_groups)

                        if own_closest_group:
                            self.move_to(group, own_closest_group.center, MoveType.ReGroup)
                        else:
                            # fight to bitter end
                            self.attack_to(group, closest_enemies.center, move_type)

        self.tags.clear()

    def faster_group_should_regroup(self, group1: CombatUnits, group2: Optional[CombatUnits]) -> bool:
        if not group2:
            return False
        if group1.average_speed < group2.average_speed + 0.1:
            return False
        # Our group is faster, it's a good idea to regroup
        return True

    def regroup(self, group: CombatUnits, target: Union[Unit, Point2]):
        if isinstance(target, Unit):
            target = self.pather.find_path(group.center, target.position, 1)
        else:
            target = self.pather.find_path(group.center, target, 3)
        self.move_to(group, target, MoveType.Push)

    def move_to(self, group: CombatUnits, target, move_type: MoveType):
        self.action_to(group, target, move_type, False)

    def attack_to(self, group: CombatUnits, target, move_type: MoveType):
        self.action_to(group, target, move_type, True)

    def action_to(self, group: CombatUnits, target, move_type: MoveType, is_attack: bool):
        if isinstance(target, Point2) and group.ground_units:
            if move_type in {MoveType.DefensiveRetreat, MoveType.PanicRetreat}:
                target = self.pather.find_influence_ground_path(group.center, target, 14)
            else:
                target = self.pather.find_path(group.center, target, 14)

        own_unit_cache: Dict[UnitTypeId, Units] = {}

        for unit in group.units:
            real_type = self.unit_values.real_type(unit.type_id)
            units = own_unit_cache.get(real_type, Units([], self.ai))
            if units.amount == 0:
                own_unit_cache[real_type] = units

            units.append(unit)

        for type_id, type_units in own_unit_cache.items():
            micro: MicroStep = self.unit_micros.get(type_id, self.generic_micro)
            micro.init_group(group, type_units, self.enemy_groups, move_type)
            group_action = micro.group_solve_combat(type_units, Action(target, is_attack))

            for unit in type_units:
                final_action = micro.unit_solve_combat(unit, group_action)
                order = final_action.to_commmand(unit)
                if order:
                    self.ai.do(order)

                if self.debug:
                    if final_action.debug_comment:
                        status = final_action.debug_comment
                    elif final_action.ability:
                        status = final_action.ability.name
                    elif final_action.is_attack:
                        status = "Attack"
                    else:
                        status = "Move"
                    if final_action.target is not None:
                        if isinstance(final_action.target, Unit):
                            status += f": {final_action.target.type_id.name}"
                        else:
                            status += f": {final_action.target}"

                    status += f" G: {group.debug_index}"
                    status += f"\n{move_type.name}"

                    pos3d: Point3 = unit.position3d
                    pos3d = Point3((pos3d.x, pos3d.y, pos3d.z + 2))
                    self.ai._client.debug_text_world(status, pos3d, size=10)

    def closest_group(self, start: Point2, combat_groups: List[CombatUnits]) -> Optional[CombatUnits]:
        group = None
        best_distance = 50  # doesn't find enemy groups closer than this

        for combat_group in combat_groups:
            center = combat_group.center

            if center == start:
                continue  # it's the same group!

            distance = start.distance_to(center)
            if distance < best_distance:
                best_distance = distance
                group = combat_group

        return group

    def group_own_units(self, our_units: Units) -> List[CombatUnits]:
        lookup_distance = self.own_group_threshold
        groups: List[Units] = []
        assigned: Dict[int, int] = dict()

        for unit in our_units:
            if unit.tag in assigned:
                continue

            units = Units([unit], self.ai)
            index = len(groups)

            assigned[unit.tag] = index

            groups.append(units)
            self.include_own_units(unit, units, lookup_distance, index, assigned)

        return [CombatUnits(u, self.knowledge) for u in groups]

    def include_own_units(self, unit: Unit, units: Units, lookup_distance: float, index: int, assigned: Dict[int, int]):
        units_close_by = self.cache.own_in_range(unit.position, lookup_distance)

        for unit_close in units_close_by:
            if unit_close.tag in assigned or unit_close.tag not in self.tags:
                continue

            assigned[unit_close.tag] = index
            units.append(unit_close)
            self.include_own_units(unit_close, units, lookup_distance, index, assigned)

    def group_enemy_units(self) -> List[CombatUnits]:
        groups: List[Units] = []
        assigned: Dict[int, int] = dict()
        lookup_distance = 7

        for unit in self.knowledge.known_enemy_units_mobile:
            if unit.tag in assigned or unit.type_id in self.unit_values.combat_ignore or not unit.can_be_attacked:
                continue

            units = Units([unit], self.ai)
            index = len(groups)

            assigned[unit.tag] = index

            groups.append(units)
            self.include_enemy_units(unit, units, lookup_distance, index, assigned)

        return [CombatUnits(u, self.knowledge) for u in groups]

    def include_enemy_units(
        self, unit: Unit, units: Units, lookup_distance: float, index: int, assigned: Dict[int, int]
    ):
        units_close_by = self.cache.enemy_in_range(unit.position, lookup_distance)

        for unit_close in units_close_by:
            if unit_close.tag in assigned or unit_close.tag not in self.tags or not unit.can_be_attacked:
                continue

            assigned[unit_close.tag] = index
            units.append(unit_close)
            self.include_enemy_units(unit_close, units, lookup_distance, index, assigned)
    def execute(self, target: Point2, move_type=MoveType.Assault):
        our_units = self.get_all_units()
        if len(our_units) < 1:
            return

        self.own_groups: List[CombatUnits] = self.group_own_units(our_units)

        total_power = ExtendedPower(self.unit_values)

        for group in self.own_groups:
            total_power.add_power(group.power)

        if self.debug:
            fn = lambda group: group.center.distance_to(self.ai.start_location)
            sorted_list = sorted(self.own_groups, key=fn)
            for i in range(0, len(sorted_list)):
                sorted_list[i].debug_index = i

        for group in self.own_groups:
            center = group.center
            closest_enemies = group.closest_target_group(self.enemy_groups)
            own_closest_group = self.closest_group(center, self.own_groups)

            if closest_enemies is None:
                if move_type == MoveType.PanicRetreat:
                    self.move_to(group, target, move_type)
                else:
                    self.attack_to(group, target, move_type)
            else:
                power = group.power
                enemy_power = ExtendedPower(closest_enemies)

                is_in_combat = group.is_in_combat(closest_enemies)

                if move_type == MoveType.DefensiveRetreat or move_type == MoveType.PanicRetreat:
                    self.move_to(group, target, move_type)
                    break

                if power.power > self.regroup_threshold * total_power.power:
                    # Most of the army is here
                    if group.is_too_spread_out() and not is_in_combat:
                        self.regroup(group, group.center)
                    else:
                        self.attack_to(group, target, move_type)

                elif is_in_combat:
                    if not power.is_enough_for(enemy_power, 0.75):
                        # Regroup if possible

                        if own_closest_group:
                            self.move_to(group, own_closest_group.center, MoveType.ReGroup)
                        else:
                            # fight to bitter end
                            self.attack_to(group, closest_enemies.center, move_type)
                    else:
                        self.attack_to(group, closest_enemies.center, move_type)
                else:
                    # if self.faster_group_should_regroup(group, own_closest_group):
                    #     self.move_to(group, own_closest_group.center, MoveType.ReGroup)

                    if group.power.is_enough_for(self.all_enemy_power, 0.85):
                        # We have enough units here to crush everything the enemy has
                        self.attack_to(group, closest_enemies.center, move_type)
                    else:
                        # Regroup if possible
                        if move_type == MoveType.Assault:
                            # Group towards attack target
                            own_closest_group = self.closest_group(target, self.own_groups)
                        else:
                            # Group up with closest group
                            own_closest_group = self.closest_group(center, self.own_groups)

                        if own_closest_group:
                            self.move_to(group, own_closest_group.center, MoveType.ReGroup)
                        else:
                            # fight to bitter end
                            self.attack_to(group, closest_enemies.center, move_type)

        self.tags.clear()
Exemple #23
0
    def _should_attack(self, power: ExtendedPower) -> bool:
        if self.attack_on_advantage and self.ai.supply_used < 190:
            if (self.game_analyzer.our_army_predict in at_least_clear_advantage
                    and self.game_analyzer.our_income_advantage
                    in at_least_small_disadvantage) or (
                        self.game_analyzer.our_army_predict
                        in at_least_small_advantage
                        and self.game_analyzer.our_income_advantage
                        in at_least_clear_disadvantage):
                # Our army is bigger but economy is weaker, attack!
                return True

            if (self.game_analyzer.our_army_predict in at_least_small_advantage
                    and self.game_analyzer.our_income_advantage
                    in at_least_small_advantage):
                # Our army is bigger and economy is better, attack!
                return True

            if (self.game_analyzer.our_army_predict
                    in at_least_small_disadvantage
                    and self.game_analyzer.our_income_advantage
                    in at_least_clear_advantage) or (
                        self.game_analyzer.our_army_predict
                        in at_least_clear_disadvantage
                        and self.game_analyzer.our_income_advantage
                        in at_least_small_advantage):
                # Our army is smaller but economy is better, focus on defence!
                return False

        enemy_total_power: ExtendedPower = self.knowledge.enemy_units_manager.enemy_total_power
        enemy_total_power.multiply(ENEMY_TOTAL_POWER_MULTIPLIER)
        multiplier = ENEMY_TOTAL_POWER_MULTIPLIER

        zone_count = 0
        for zone in self.knowledge.expansion_zones:  # type: Zone
            if zone.is_enemys:
                zone_count += 1

        enemy_main: Zone = self.knowledge.expansion_zones[-1]
        enemy_natural: Zone = self.knowledge.expansion_zones[-2]

        if zone_count < self.cache.own_townhalls.amount and self.knowledge.enemy_race == Race.Terran:
            multiplier *= 1.1

        if zone_count > 2 and self.cache.own_townhalls.amount > 2:
            multiplier = 1.1

        if zone_count == 1 and enemy_main.is_enemys:
            # We should seriously consider whether we want to crash and burn against a one base defense
            enemy_total_power.add_units(enemy_main.enemy_static_defenses.ready)
            if self.ai.time <= 6 * 60 + 20 and self.knowledge.enemy_race != Race.Zerg:
                snap_shot_num = self.cache.enemy(
                    [UnitTypeId.SIEGETANKSIEGED, UnitTypeId.CYCLONE, UnitTypeId.BUNKER])\
                    .filter(lambda u: u.is_snapshot).amount
                if self.knowledge.enemy_units_manager.unit_count(UnitTypeId.SIEGETANK) + \
                        self.knowledge.enemy_units_manager.unit_count(UnitTypeId.SIEGETANKSIEGED) + \
                        self.knowledge.enemy_units_manager.unit_count(UnitTypeId.CYCLONE) + \
                        self.knowledge.enemy_units_manager.unit_count(UnitTypeId.LIBERATORAG) + \
                        self.knowledge.enemy_units_manager.unit_count(UnitTypeId.BUNKER) + \
                        snap_shot_num >= 1:
                    multiplier *= 4.2
                    return False
                else:
                    multiplier *= 3.8
                    return False

            if self.cache.own_townhalls.amount == 2:
                multiplier *= 1.8
                return False

        elif zone_count == 2 and enemy_natural.is_enemys:
            enemy_total_power.add_units(enemy_natural.enemy_static_defenses)
            if self.cache.own_townhalls.amount == 1 and self.knowledge.enemy_race != Race.Zerg:
                multiplier *= 0.4
            if self.cache.own_townhalls.amount == 2 and self.knowledge.enemy_race != Race.Zerg:
                multiplier *= 0.7
            if self.cache.own_townhalls.amount == 2 and self.knowledge.enemy_race == Race.Terran:
                multiplier *= 1.1
                return False
            if self.cache.own_townhalls.amount == 2 and self.knowledge.enemy_race == Race.Protoss:
                multiplier *= 1.1
            if self.cache.own_townhalls.ready.amount == 3 and self.knowledge.enemy_race == Race.Terran:
                multiplier *= 2.4
            if self.cache.own_townhalls.ready.amount == 3 and self.knowledge.enemy_race == Race.Protoss:
                multiplier *= 1.4

            if (self.knowledge.enemy_race == Race.Terran
                    and (self.knowledge.enemy_units_manager.unit_count(
                        UnitTypeId.PLANETARYFORTRESS) >= 1
                         or self.knowledge.enemy_units_manager.unit_count(
                             UnitTypeId.BUNKER) >= 1)):
                multiplier *= 2.2
            elif (self.knowledge.enemy_race == Race.Terran
                  and (self.knowledge.enemy_units_manager.unit_count(
                      UnitTypeId.SIEGETANK) >= 1
                       or self.knowledge.enemy_units_manager.unit_count(
                           UnitTypeId.WIDOWMINE) >= 1)):
                multiplier *= 2

        elif zone_count == 3 and enemy_natural.is_enemys:
            if self.cache.own_townhalls.amount == 2:
                multiplier *= 0.5
            if self.cache.own_townhalls.amount == 3 and self.knowledge.enemy_race == Race.Terran:
                multiplier *= 0.6
            if (self.knowledge.enemy_race == Race.Terran
                    and (self.knowledge.enemy_units_manager.unit_count(
                        UnitTypeId.SIEGETANK) >= 1
                         or self.knowledge.enemy_units_manager.unit_count(
                             UnitTypeId.WIDOWMINE) >= 1)):
                multiplier *= 1.2

        if zone_count - self.cache.own_townhalls.amount >= 2 and self.knowledge.enemy_race == Race.Terran:
            multiplier = 0.8

        if zone_count - self.cache.own_townhalls.amount > 1 and self.knowledge.enemy_race == Race.Zerg:
            multiplier *= 0.7

        if self.ai.time >= 4 * 60 + 45 and self.knowledge.enemy_race == Race.Zerg:
            multiplier *= 0.3
        if self.ai.time < 4 * 60 + 45 and self.knowledge.enemy_race == Race.Zerg:
            multiplier *= 20

        if self.ai.time >= 8 * 60 and self.knowledge.enemy_race != Race.Zerg:
            multiplier = 1.1

        enemy_total_power.power = max(self.start_attack_power,
                                      enemy_total_power.power)

        if power.is_enough_for(enemy_total_power, 1 / multiplier):
            self.print(
                f"Power {power.power:.2f} is larger than required attack power {enemy_total_power.power:.2f} -> attack!"
                f"--------------------------------------------------------------------{1 / multiplier:.2f}"
            )
            return True
        if self.ai.supply_used >= 188:
            self.print(f"Supply is {self.ai.supply_used} -> attack!")
            return True
        return False
class CombatUnits:
    def __init__(self, units: Units, knowledge: "Knowledge"):
        self.knowledge = knowledge
        self.unit_values = knowledge.unit_values
        self.units = units
        self.center: Point2 = sc2math.unit_geometric_median(units)
        self.ground_units = self.units.not_flying
        if self.ground_units:
            self.center: Point2 = self.ground_units.closest_to(
                (self.center)).position

        self.power = ExtendedPower(self.unit_values)
        self.power.add_units(self.units)
        self.debug_index = 0
        self._total_distance: Optional[float] = None
        self._area_by_circles: float = 0
        self.average_speed = 0

        for unit in self.units:
            self.average_speed += knowledge.unit_values.real_speed(unit)

        if len(self.units) > 1:
            self.average_speed /= len(self.units)

    def is_too_spread_out(self) -> bool:
        if self._total_distance is None:
            self._total_distance = 0
            self._area_by_circles = 5

            for unit in self.units:
                d = unit.distance_to(self.center)
                if unit.energy_percentage >= 0.4:
                    d *= 2
                self._total_distance += d
                self._area_by_circles += unit.radius**2
        # self.knowledge.print(f"spread: {self._total_distance} d to {self._total_radius} r")
        return (self._total_distance /
                len(self.units))**2 > self._area_by_circles * 2

    def is_in_combat(self, closest_enemies: "CombatUnits") -> bool:
        if closest_enemies is None:
            return False

        distance = self.center.distance_to_point2(closest_enemies.center)
        if distance > 17:
            return False

        if distance < 10 or self.knowledge.unit_cache.enemy_in_range(
                self.center, 10).exclude_type(self.unit_values.combat_ignore):
            return True

        engaged_power = 0
        total_power = 0

        for unit in self.units:  # type: Unit
            power = self.unit_values.power(unit)
            total_power += power

            for enemy_near in closest_enemies.units:
                d = enemy_near.distance_to(unit)
                if d < self.unit_values.real_range(unit, enemy_near):
                    engaged_power += power
                    break

        return engaged_power > total_power * 0.15

    def closest_target_group(
            self,
            combat_groups: List["CombatUnits"]) -> Optional["CombatUnits"]:
        group = None
        start = self.center
        best_distance = 50  # doesn't find enemy groups closer than this

        shoots_air = self.power.air_power > 0
        shoots_ground = self.power.ground_power > 0

        for combat_group in combat_groups:
            if not combat_group.ground_units and not shoots_air:
                continue  # We can't shoot the targets here
            if combat_group.power.air_presence == 0 and combat_group.power.ground_presence > 0 and not shoots_ground:
                continue  # We can't shoot the targets here

            if combat_group.power.air_presence > 0 and combat_group.power.ground_presence == 0 and not shoots_air:
                continue  # We can't shoot the targets here

            center = combat_group.center

            distance = start.distance_to(center)
            if distance < best_distance:
                best_distance = distance
                group = combat_group

        return group
Exemple #25
0
 async def start(self, knowledge: "Knowledge"):
     await super().start(knowledge)
     self.engaged_power = ExtendedPower(knowledge.unit_values)
     self.our_power = ExtendedPower(knowledge.unit_values)
    def handle_groups(combat: "GroupCombatManager",
                      target: Point2,
                      move_type=MoveType.Assault):
        total_power = ExtendedPower(combat.unit_values)

        for group in combat.own_groups:
            total_power.add_power(group.power)

        for group in combat.own_groups:
            if not combat.rules.regroup or combat.regroup_threshold <= 0:
                # Skip all regroup logic
                if move_type == MoveType.PanicRetreat:
                    combat.move_to(group, target, move_type)
                else:
                    combat.attack_to(group, target, move_type)
                continue

            center = group.center
            closest_enemies = group.closest_target_group(combat.enemy_groups)
            own_closest_group = combat.closest_group(center, combat.own_groups)

            if closest_enemies is None:
                if move_type == MoveType.PanicRetreat:
                    combat.move_to(group, target, move_type)
                else:
                    combat.attack_to(group, target, move_type)
            else:
                power = group.power
                enemy_power = ExtendedPower(closest_enemies)

                is_in_combat = group.is_in_combat(closest_enemies)

                if move_type == MoveType.DefensiveRetreat or move_type == MoveType.PanicRetreat:
                    combat.move_to(group, target, move_type)
                    break

                if power.power > combat.regroup_threshold * total_power.power:
                    # Most of the army is here
                    if group.is_too_spread_out(
                    ) and not is_in_combat and enemy_power.power > 5:
                        combat.regroup(group, group.center)
                    else:
                        combat.attack_to(group, target, move_type)

                elif is_in_combat:
                    if not power.is_enough_for(enemy_power, 0.75):
                        # Regroup if possible

                        if own_closest_group:
                            combat.move_to(group, own_closest_group.center,
                                           MoveType.ReGroup)
                        else:
                            # fight to bitter end
                            combat.attack_to(group, closest_enemies.center,
                                             move_type)
                    else:
                        combat.attack_to(group, closest_enemies.center,
                                         move_type)
                else:
                    # if self.faster_group_should_regroup(group, own_closest_group):
                    #     self.move_to(group, own_closest_group.center, MoveType.ReGroup)

                    if group.power.is_enough_for(combat.all_enemy_power, 0.85):
                        # We have enough units here to crush everything the enemy has
                        combat.attack_to(group, closest_enemies.center,
                                         move_type)
                    else:
                        # Regroup if possible
                        if move_type == MoveType.Assault:
                            # Group towards attack target
                            own_closest_group = combat.closest_group(
                                target, combat.own_groups)
                        else:
                            # Group up with closest group
                            own_closest_group = combat.closest_group(
                                center, combat.own_groups)

                        if own_closest_group:
                            combat.move_to(group, own_closest_group.center,
                                           MoveType.ReGroup)
                        else:
                            # fight to bitter end
                            combat.attack_to(group, closest_enemies.center,
                                             move_type)
Exemple #27
0
    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):
                positions = map(lambda u: u.position,
                                enemies)  # need to be specified in both places
                s_range = self.unit_values.ground_range(example_enemy)
                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])
Exemple #28
0
    async def execute(self) -> bool:
        unit: Unit

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

        for index in range(0, len(self.knowledge.expansion_zones)):

            zone: "Zone" = self.knowledge.expansion_zones[index]
            zone_tags = self.defender_tags[index]

            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 not 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[index] +
                            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[index] = 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)
                if enemies.exists:
                    # is main force
                    if enemies.amount >= 7:
                        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)

                # or (len(enemies) == 1 and enemies[0].type_id not in self.unit_values.worker_types)
                if len(enemies) >= 1:
                    # 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.roles.refresh_tags(self.combat.tags)
                self.combat.execute(enemy_center, MoveType.SearchAndDestroy)
        return True  # never block
Exemple #29
0
    def __init__(self, center_location, is_start_location, knowledge):
        self.center_location: Point2 = center_location
        self.is_start_location: bool = is_start_location

        self.knowledge = knowledge
        self.ai: sc2.BotAI = knowledge.ai
        self.cache: "UnitCacheManager" = knowledge.unit_cache
        self.unit_values: "UnitValue" = knowledge.unit_values
        self.needs_evacuation = False
        self._is_enemys = False

        self.zone_index: int = 0
        self.paths: Dict[int, Path] = dict(
        )  # paths to other expansions as it is dictated in the .expansion_zones
        # Game time seconds when we have last had visibility on this zone.
        self.last_scouted_center: float = -1
        self.last_scouted_mineral_line: float = -1

        # Timing on when there could be enemy workers here
        self.could_have_enemy_workers_in = 0

        # All mineral fields on the zone
        self._original_mineral_fields: Units = self.ai.expansion_locations_dict.get(
            self.center_location, Units([], self.ai))
        self.mineral_fields: Units = Units(
            self._original_mineral_fields.copy(), self.ai)

        self.last_minerals: int = 10000000  # Arbitrary value just to ensure a lower value will get updated.
        # Game time seconds when scout has last circled around the center location of this zone.

        # All vespene geysers on the zone
        self.gas_buildings: Units = None

        self.scout_last_circled: Optional[int] = None

        self.our_townhall: Optional[Unit] = None
        self.enemy_townhall: Optional[Unit] = None

        self.known_enemy_units: Units = Units([], self.ai)
        self.our_units: Units = Units([], self.ai)
        self.our_workers: Units = Units([], self.ai)
        self.enemy_workers: Units = Units([], self.ai)
        self.known_enemy_power: ExtendedPower = ExtendedPower(self.unit_values)
        self.our_power: ExtendedPower = ExtendedPower(self.unit_values)

        # Assaulting enemies can be further away, but zone defense should prepare for at least that amount of defense
        self.assaulting_enemies: Units = Units([], self.ai)
        self.assaulting_enemy_power: ExtendedPower = ExtendedPower(
            self.unit_values)

        # 3 positions behind minerals
        self.behind_mineral_positions: List[
            Point2] = self._init_behind_mineral_positions()
        self._count_minerals()
        self._minerals_counter = IntervalFunc(knowledge.ai,
                                              self._count_minerals, 0.5)
        self.gather_point = self.center_location.towards(
            self.ai.game_info.map_center, 3)

        self.height = self.ai.get_terrain_height(center_location)

        # This is ExtendedRamp!
        self.ramp = self._find_ramp(self.ai)
        self.radius = Zone.ZONE_RADIUS
        self.danger_radius = Zone.ZONE_DANGER_RADIUS

        if self.ramp is not None:
            self.gather_point = self.ramp.top_center.towards(
                self.center_location, 4)
Exemple #30
0
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)