diff --git a/src/ares/behaviors/combat/group/stutter_group_back.py b/src/ares/behaviors/combat/group/stutter_group_back.py index 5154ac3..531957d 100644 --- a/src/ares/behaviors/combat/group/stutter_group_back.py +++ b/src/ares/behaviors/combat/group/stutter_group_back.py @@ -49,7 +49,6 @@ def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> boo sample_unit: Unit = sorted_units[0] if self.group_weapons_on_cooldown(self.group, stutter_forward=False): - group_safe: bool = True for unit in self.group: if not mediator.is_position_safe( diff --git a/src/ares/build_runner/build_order_parser.py b/src/ares/build_runner/build_order_parser.py index f085c94..920ccfa 100644 --- a/src/ares/build_runner/build_order_parser.py +++ b/src/ares/build_runner/build_order_parser.py @@ -312,6 +312,14 @@ def _parse_string_command( if _duplicates := self.extract_integer_from_target(target): duplicates = _duplicates + if command == BuildOrderOptions.CHRONO and not step.target: + raise Exception( + f"No target found for chrono build step command. \n" + f"Valid example: " + f"``` 16 chrono @ nexus ``` \n" + f"Found: {raw_step}" + ) + step.start_at_supply = supply for i in range(duplicates): build_order.append(step) @@ -389,7 +397,10 @@ def extract_integer_from_target(target: str) -> Optional[int]: def _get_target_for_step(target: str) -> Union[str, UnitID]: """Set the target for the step.""" try: - return UnitID[target] + if target == BuildOrderOptions.CORE: + return UnitID.CYBERNETICSCORE + else: + return UnitID[target] except KeyError: try: return BuildOrderTargetOptions[target] diff --git a/src/ares/build_runner/build_order_runner.py b/src/ares/build_runner/build_order_runner.py index 2b3d491..dc604d4 100644 --- a/src/ares/build_runner/build_order_runner.py +++ b/src/ares/build_runner/build_order_runner.py @@ -136,7 +136,8 @@ def set_build_completed(self) -> None: def set_step_complete(self, value: UnitID) -> None: if ( - value == self.build_order[self.build_step].command + self.build_step < len(self.build_order) + and value == self.build_order[self.build_step].command and self.current_step_started ): self.current_step_complete = True @@ -415,8 +416,9 @@ async def get_position( ] if len(close_enemy_to_ramp) > 0: at_wall = False + base_location = self._get_target(target) return self.mediator.request_building_placement( - base_location=self.ai.start_location, + base_location=base_location, structure_type=structure_type, wall=at_wall, within_psionic_matrix=within_psionic_matrix, diff --git a/src/ares/managers/placement_manager.py b/src/ares/managers/placement_manager.py index feffbad..f4a33bb 100644 --- a/src/ares/managers/placement_manager.py +++ b/src/ares/managers/placement_manager.py @@ -486,6 +486,17 @@ def request_building_placement( # prioritize production pylons if they exist elif structure_type == UnitID.PYLON: if available := [ + a + for a in available + if self.placements_dict[location][building_size][a]["optimal_pylon"] + # don't wall in, user should intentionally pass wall parameter + and not self.placements_dict[location][building_size][a]["is_wall"] + ]: + final_placement = min( + available, + key=lambda k: cy_distance_to_squared(k, base_location), + ) + elif available := [ a for a in available if self.placements_dict[location][building_size][a][ @@ -500,8 +511,20 @@ def request_building_placement( ) if self.ai.race == Race.Protoss and within_psionic_matrix: + build_near: Point2 = location + two_by_twos: dict = self.placements_dict[location][ + BuildingSize.TWO_BY_TWO + ] + if optimal_pylon := [ + a + for a in two_by_twos + if self.placements_dict[location][BuildingSize.TWO_BY_TWO][a][ + "optimal_pylon" + ] + ]: + build_near = optimal_pylon[0] final_placement = self._find_placement_near_pylon( - available, base_location, pylon_build_progress + available, build_near, pylon_build_progress ) if not final_placement: logger.warning( @@ -821,7 +844,7 @@ def _solve_protoss_building_formation(self): start_x: int = int(el.x - 4.5) start_y: int = int(el.y - 4.5) self.points_to_avoid_grid[start_y : start_y + 9, start_x : start_x + 9] = 1 - max_dist = 16 + max_dist: int = 16 # calculate the wall positions first if el == self.ai.start_location: max_dist = 22 @@ -837,8 +860,8 @@ def _solve_protoss_building_formation(self): # find production pylon positions first production_pylon_positions = cy_find_building_locations( kernel=np.ones((2, 2), dtype=np.uint8), - x_stride=6, - y_stride=6, + x_stride=7, + y_stride=7, x_bounds=raw_x_bounds, y_bounds=raw_y_bounds, creep_grid=creep_grid, @@ -867,10 +890,6 @@ def _solve_protoss_building_formation(self): avoid_y : avoid_y + 2, avoid_x : avoid_x + 2 ] = 1 - # increase distance from townhall that should be avoided - start_x: int = int(el.x - 4.5) - start_y: int = int(el.y - 4.5) - self.points_to_avoid_grid[start_y : start_y + 9, start_x : start_x + 9] = 1 three_by_three_positions = cy_find_building_locations( kernel=np.ones((3, 3), dtype=np.uint8), x_stride=3, @@ -928,6 +947,38 @@ def _solve_protoss_building_formation(self): BuildingSize.TWO_BY_TWO, el, point2_pos ) + # find optimal pylon to build around (fits most 3x3) + self._find_optimal_pylon_for_base(el) + + def _find_optimal_pylon_for_base(self, el: Point2) -> None: + two_by_twos: dict[Point2:dict] = self.placements_dict[el][ + BuildingSize.TWO_BY_TWO + ] + prod_pylons: list[Point2] = [ + placement + for placement in two_by_twos + if two_by_twos[placement]["production_pylon"] + ] + three_by_threes: dict[Point2:dict] = self.placements_dict[el][ + BuildingSize.THREE_BY_THREE + ] + + best_pylon_pos: Point2 = el + most_three_by_threes: int = 0 + + for pylon_position in prod_pylons: + num_three_by_threes: int = 0 + for three_by_three in three_by_threes: + if cy_distance_to_squared(pylon_position, three_by_three) < 42.25: + num_three_by_threes += 1 + if num_three_by_threes > most_three_by_threes: + most_three_by_threes = num_three_by_threes + best_pylon_pos = pylon_position + + self.placements_dict[el][BuildingSize.TWO_BY_TWO][best_pylon_pos][ + "optimal_pylon" + ] = True + def _solve_zerg_building_formation(self): # TODO: Implement zerg placements pass @@ -940,6 +991,7 @@ def _add_placement_position( production_pylon: bool = False, wall: bool = False, bunker: bool = False, + optimal_pylon: bool = False, ) -> None: """Add calculated position to placements dict.""" self.placements_dict[expansion_location][building_size][position] = { @@ -951,6 +1003,7 @@ def _add_placement_position( "time_requested": 0.0, "production_pylon": production_pylon, "bunker": bunker, + "optimal_pylon": optimal_pylon, } def _calculate_main_ramp_placements(self, el: Point2) -> None: @@ -1035,7 +1088,9 @@ async def _draw_building_placements(self): self.ai.draw_text_on_world(position, f"{placement}") pos_min = Point3((placement.x - 1.0, placement.y - 1.0, z)) pos_max = Point3((placement.x + 1.0, placement.y + 1.0, z + 1)) - if info["production_pylon"]: + if info["optimal_pylon"]: + colour = Point3((255, 0, 0)) + elif info["production_pylon"]: colour = Point3((0, 255, 0)) else: colour = Point3((0, 0, 255))