From da222ab709f1536eee6ef5120d139976997a8c48 Mon Sep 17 00:00:00 2001 From: Tom Kerr Date: Tue, 21 Nov 2023 12:49:27 +0000 Subject: [PATCH 1/6] feat: add reaper grenade to grids --- src/ares/config.yml | 3 +++ src/ares/consts.py | 3 +++ src/ares/managers/path_manager.py | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ares/config.yml b/src/ares/config.yml index 244c6b9..a9f1cde 100644 --- a/src/ares/config.yml +++ b/src/ares/config.yml @@ -54,6 +54,9 @@ Pathing: CorrosiveBile: Cost: 100 Range: 3.0 + KD8Charge: + Cost: 10 + Range: 4.5 LiberatorZone: Cost: 500 Range: 6 diff --git a/src/ares/consts.py b/src/ares/consts.py index d8b0d67..b5cc753 100644 --- a/src/ares/consts.py +++ b/src/ares/consts.py @@ -44,6 +44,7 @@ GROUND_ENEMY_LEAVING_BASES: str = "GroundEnemyLeavingBases" GROUND_ENEMY_NEAR_BASES: str = "GroundEnemyNearBases" GROUND_RANGE: str = "GroundRange" +KD8_CHARGE: str = "KD8Charge" LIBERATOR_ZONE: str = "LiberatorZone" LURKER_SPINE: str = "LurkerSpine" MINERAL_BOOST: str = "MineralBoost" @@ -355,9 +356,11 @@ class UnitRole(str, Enum): HARASSING = "HARASSING" # units that are harassing IDLE = "IDLE" # not doing anything PERSISTENT_BUILDER = "PERSISTENT_BUILDER" # does not get reassigned automatically + PROXY_WORKER = "PROXY_WORKER" REPAIRING = "REPAIRING" # repairing scvs SCOUTING = "SCOUTING" SURROUNDING = "SURROUNDING" # zerglings currently in a surround + UNDER_REPAIR = "UNDER_REPAIR" # units currently under repair # control groups, use for anything not specified CONTROL_GROUP_ONE = "CONTROL_GROUP_ONE" CONTROL_GROUP_TWO = "CONTROL_GROUP_TWO" diff --git a/src/ares/managers/path_manager.py b/src/ares/managers/path_manager.py index 066a9e8..67e5d7d 100644 --- a/src/ares/managers/path_manager.py +++ b/src/ares/managers/path_manager.py @@ -48,8 +48,11 @@ UNITS, ManagerName, ManagerRequestType, + KD8_CHARGE, + TOWNHALL_TYPES, ) from ares.cython_extensions.combat_utils import cy_is_position_safe +from ares.cython_extensions.geometry import cy_distance_to from ares.dicts.weight_costs import WEIGHT_COSTS from ares.managers.manager import Manager from ares.managers.manager_mediator import IManagerMediator, ManagerMediator @@ -667,6 +670,18 @@ def _add_effects(self) -> None: + self.config[PATHING][EFFECTS_RANGE_BUFFER], [self.climber_grid, self.ground_grid, self.ground_avoidance_grid], ) + elif effect.id == "KD8CHARGE": + ( + self.climber_grid, + self.ground_grid, + # self.ground_avoidance_grid, + ) = self.add_cost_to_multiple_grids( + Point2.center(effect.positions), + effect_values[KD8_CHARGE][COST], + effect_values[KD8_CHARGE][RANGE] + + self.config[PATHING][EFFECTS_RANGE_BUFFER], + [self.climber_grid, self.ground_grid], + ) # liberator siege elif effect.id in { EffectId.LIBERATORTARGETMORPHDELAYPERSISTENT, @@ -803,6 +818,11 @@ def _add_structure_influence(self, structure: Unit) -> None: [self.air_grid, self.air_vs_ground_grid], ) elif structure.type_id == UnitID.BUNKER: + if self.ai.enemy_structures.filter( + lambda g: g.type_id in TOWNHALL_TYPES + and cy_distance_to(g.position, structure.position) < 9.0 + ): + return # add range of marine + 1 ( self.air_grid, @@ -822,7 +842,10 @@ def _add_structure_influence(self, structure: Unit) -> None: ) elif structure.type_id == UnitID.PLANETARYFORTRESS: s_range: int = 7 if self.ai.time > 400 else 6 - (self.climber_grid, self.ground_grid,) = self.add_cost_to_multiple_grids( + ( + self.climber_grid, + self.ground_grid, + ) = self.add_cost_to_multiple_grids( structure.position, 28, s_range + self.config[PATHING][RANGE_BUFFER], From fc77ddb1279965ec933165c7a12ea3d119155668 Mon Sep 17 00:00:00 2001 From: Tom Kerr Date: Tue, 21 Nov 2023 12:50:06 +0000 Subject: [PATCH 2/6] feat: improve worker safety behavior --- src/ares/behaviors/macro/mining.py | 72 +++++++++++++++++++----------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/ares/behaviors/macro/mining.py b/src/ares/behaviors/macro/mining.py index 5534cde..d28b776 100644 --- a/src/ares/behaviors/macro/mining.py +++ b/src/ares/behaviors/macro/mining.py @@ -110,19 +110,6 @@ def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> boo resource_position: Optional[Point2] = None resource_tag: int = -1 - # keeping worker safe is first priority - if self.keep_safe and ( - # lib zone / nukes etc - not pos_safe(grid=avoidance_grid, position=worker_position) - # retreat based on self.flee_at_health_perc value - or ( - worker.health_percentage <= health_perc - and not pos_safe(grid=grid, position=worker_position) - ) - ): - self._keep_worker_safe(mediator, grid, worker, worker_position) - continue - assigned_mineral_patch: bool = worker_tag in worker_to_mineral_patch_dict assigned_gas_building: bool = worker_tag in worker_to_geyser_dict dist_to_resource: float = 15.0 @@ -149,6 +136,26 @@ def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> boo mediator.remove_gas_building(gas_building_tag=resource_tag) continue + # keeping worker safe is first priority + if self.keep_safe and ( + # lib zone / nukes etc + not pos_safe(grid=avoidance_grid, position=worker_position) + # retreat based on self.flee_at_health_perc value + or ( + worker.health_percentage <= health_perc + and not pos_safe(grid=grid, position=worker_position) + ) + ): + self._keep_worker_safe( + mediator, + grid, + worker, + resource, + resource_position, + dist_to_resource, + ) + continue + if main_enemy_ground_threats and self._worker_attacking_enemy( ai, dist_to_resource, worker ): @@ -218,29 +225,36 @@ def _keep_worker_safe( mediator: ManagerMediator, grid: np.ndarray, worker: Unit, - worker_position: Point2, + resource: Unit, + resource_position: Point2, + dist_to_resource: float, ) -> None: """Logic for keeping workers in danger safe. Parameters ---------- - mediator : ManagerMediator + mediator : ManagerMediator used for getting information from other managers. - grid : np.ndarray + grid : Ground grid with enemy influence. worker : Worker to keep safe. - worker_position : - Pass this in for optimization purposes, - as may have already retrieved it. + resource : + Resource worker is assigned to. + resource_position : + Resource this worker is assigned to. + dist_to_resource : + How far away are we from intended resource? Returns ------- - """ - worker.move( - mediator.find_closest_safe_spot(from_pos=worker_position, grid=grid) - ) + if dist_to_resource > 5.0: + worker.gather(resource) + else: + worker.move( + mediator.find_closest_safe_spot(from_pos=resource_position, grid=grid) + ) def _do_standard_mining(self, ai: "AresBot", worker: Unit, resource: Unit) -> None: worker_tag: int = worker.tag @@ -335,9 +349,15 @@ def _long_distance_mining( target_mineral = cy_closest_to(target_base.position, ai.mineral_field) # no pending base, find a mineral field elif self.safe_long_distance_mineral_fields: - target_mineral = self.safe_long_distance_mineral_fields.closest_to( - worker - ) + # early game, get closest to natural + if ai.time < 260.0: + target_mineral = cy_closest_to( + mediator.get_own_nat, self.safe_long_distance_mineral_fields + ) + else: + target_mineral = cy_closest_to( + worker.position, self.safe_long_distance_mineral_fields + ) if target_mineral: target_mineral_position: Point2 = target_mineral.position From 730a287f46ab71f53911009a4eb89ae19714f43c Mon Sep 17 00:00:00 2001 From: Tom Kerr Date: Tue, 21 Nov 2023 12:51:26 +0000 Subject: [PATCH 3/6] feat: persistent worker bool toggle when declaring builds --- src/ares/build_runner/build_order_runner.py | 16 ++++++++++++---- src/ares/managers/building_manager.py | 5 ----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ares/build_runner/build_order_runner.py b/src/ares/build_runner/build_order_runner.py index e162955..7fea038 100644 --- a/src/ares/build_runner/build_order_runner.py +++ b/src/ares/build_runner/build_order_runner.py @@ -60,6 +60,7 @@ class BuildOrderRunner: """ CONSTANT_WORKER_PRODUCTION_TILL: str = "ConstantWorkerProductionTill" + PERSISTENT_WORKER: str = "PersistentWorker" REQUIRES_TOWNHALL_COMMANDS: set = { AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS, @@ -76,12 +77,18 @@ def __init__( self.config: dict = config self.mediator: ManagerMediator = mediator self.constant_worker_production_till: int = 0 + self.persistent_worker: bool = True self._chosen_opening: str = chosen_opening if BUILDS in self.config: build: list[str] = config[BUILDS][chosen_opening][OPENING_BUILD_ORDER] - self.constant_worker_production_till = config[BUILDS][chosen_opening][ - self.CONSTANT_WORKER_PRODUCTION_TILL - ] + if self.CONSTANT_WORKER_PRODUCTION_TILL in config[BUILDS][chosen_opening]: + self.constant_worker_production_till = config[BUILDS][chosen_opening][ + self.CONSTANT_WORKER_PRODUCTION_TILL + ] + if self.PERSISTENT_WORKER in config[BUILDS][chosen_opening]: + self.persistent_worker = config[BUILDS][chosen_opening][ + self.PERSISTENT_WORKER + ] else: build: list[str] = [] @@ -117,7 +124,8 @@ async def run_build(self) -> None: """ Runs the build order. """ - self._assign_persistent_worker() + if self.persistent_worker: + self._assign_persistent_worker() if len(self.build_order) > 0: await self.do_step(self.build_order[self.build_step]) diff --git a/src/ares/managers/building_manager.py b/src/ares/managers/building_manager.py index 28bff75..756c306 100644 --- a/src/ares/managers/building_manager.py +++ b/src/ares/managers/building_manager.py @@ -221,11 +221,6 @@ def _handle_construction_orders(self) -> None: tags_to_remove.add(worker_tag) continue - # this happens if no target location is available eg: all expansions taken - if not target: - tags_to_remove.add(worker_tag) - continue - # TODO: find the maximum distance so we don't have to keep adjusting this distance: float = 3.2 if structure_id in GAS_BUILDINGS else 1.0 From 80a16d59ddb17ac15ff071b1061df8946533293d Mon Sep 17 00:00:00 2001 From: Tom Kerr Date: Tue, 21 Nov 2023 12:52:08 +0000 Subject: [PATCH 4/6] feat: request placement closest to desired position --- src/ares/managers/placement_manager.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ares/managers/placement_manager.py b/src/ares/managers/placement_manager.py index c47fca8..f5c55fb 100644 --- a/src/ares/managers/placement_manager.py +++ b/src/ares/managers/placement_manager.py @@ -231,6 +231,7 @@ def request_building_placement( reserve_placement: bool = True, within_psionic_matrix: bool = False, pylon_build_progress: float = 1.0, + closest_to: Optional[Point2] = None, ) -> Optional[Point2]: """Given a base location and building size find an available placement. @@ -254,6 +255,8 @@ def request_building_placement( Protoss specific -> calculated position have power? pylon_build_progress : float, optional (default = 1.0) Only relevant if `within_psionic_matrix = True` + closest_to : Point2, optional + Find placement at base closest to this Returns ---------- @@ -333,9 +336,15 @@ def request_building_placement( return # get closest available by default - final_placement: Point2 = min( - available, key=lambda k: cy_distance_to(k, base_location) - ) + if not closest_to: + final_placement: Point2 = min( + available, key=lambda k: cy_distance_to(k, base_location) + ) + else: + final_placement: Point2 = min( + available, key=lambda k: cy_distance_to(k, closest_to) + ) + # if wall placement is requested swap final_placement if possible if wall: if _available := [ From 1cc50aa321182105be65eee15c7bfb5f632f6f9d Mon Sep 17 00:00:00 2001 From: Tom Kerr Date: Tue, 21 Nov 2023 12:53:45 +0000 Subject: [PATCH 5/6] fix: workaround to bug where resource bookkeeping is incorrect Not able to find root cause of this rare bug, so this workaround is here for now --- src/ares/managers/resource_manager.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ares/managers/resource_manager.py b/src/ares/managers/resource_manager.py index ef88f58..b5ccf63 100644 --- a/src/ares/managers/resource_manager.py +++ b/src/ares/managers/resource_manager.py @@ -244,6 +244,17 @@ async def update(self, iteration: int) -> None: if th.tag not in self.cached_townhalls.tags and th.is_ready: self.cached_townhalls.append(th) + # TODO: this is here to fix a rare but, where a building worker is + # selected but somehow it remains in this manager's bookkeeping + # FIX THIS! + if iteration % 32 == 0: + for worker in workers: + if worker.tag in self.manager_mediator.get_building_tracker_dict: + self.remove_worker_from_mineral(worker.tag) + self.manager_mediator.assign_role( + tag=worker.tag, role=UnitRole.BUILDING + ) + if self.debug and self.config[DEBUG_OPTIONS][RESOURCE_DEBUG]: self._print_debug_information() From d74798639a0e57a68be8ea98de8d8d603e7195d7 Mon Sep 17 00:00:00 2001 From: Tom Kerr Date: Tue, 21 Nov 2023 12:55:27 +0000 Subject: [PATCH 6/6] feat: select worker with min health perc argument --- src/ares/managers/path_manager.py | 9 +++---- src/ares/managers/resource_manager.py | 39 ++++++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/ares/managers/path_manager.py b/src/ares/managers/path_manager.py index 67e5d7d..f2d3881 100644 --- a/src/ares/managers/path_manager.py +++ b/src/ares/managers/path_manager.py @@ -34,6 +34,7 @@ GROUND_AVOIDANCE, GROUND_COST, GROUND_RANGE, + KD8_CHARGE, LIBERATOR_ZONE, LURKER_SPINE, NUKE, @@ -45,11 +46,10 @@ RANGE_BUFFER, SHOW_PATHING_COST, STORM, + TOWNHALL_TYPES, UNITS, ManagerName, ManagerRequestType, - KD8_CHARGE, - TOWNHALL_TYPES, ) from ares.cython_extensions.combat_utils import cy_is_position_safe from ares.cython_extensions.geometry import cy_distance_to @@ -842,10 +842,7 @@ def _add_structure_influence(self, structure: Unit) -> None: ) elif structure.type_id == UnitID.PLANETARYFORTRESS: s_range: int = 7 if self.ai.time > 400 else 6 - ( - self.climber_grid, - self.ground_grid, - ) = self.add_cost_to_multiple_grids( + (self.climber_grid, self.ground_grid,) = self.add_cost_to_multiple_grids( structure.position, 28, s_range + self.config[PATHING][RANGE_BUFFER], diff --git a/src/ares/managers/resource_manager.py b/src/ares/managers/resource_manager.py index b5ccf63..f8cfae4 100644 --- a/src/ares/managers/resource_manager.py +++ b/src/ares/managers/resource_manager.py @@ -23,6 +23,7 @@ UnitRole, UnitTreeQueryType, ) +from ares.cython_extensions.geometry import cy_distance_to from ares.cython_extensions.units_utils import cy_closest_to, cy_sorted_by_distance_to from ares.managers.manager import Manager from ares.managers.manager_mediator import IManagerMediator, ManagerMediator @@ -313,6 +314,7 @@ def select_worker( force_close: bool = False, select_persistent_builder: bool = False, only_select_persistent_builder: bool = False, + min_health_perc: float = 0.0, ) -> Optional[Unit]: """Select a worker. @@ -335,6 +337,8 @@ def select_worker( If True we can select the persistent_builder if it's available. only_select_persistent_builder : If True, don't find an alternative worker + min_health_perc : + Only select workers above this health percentage. Returns ------- @@ -361,7 +365,7 @@ def select_worker( workers: Units = self.manager_mediator.get_units_from_roles( roles={UnitRole.GATHERING, UnitRole.IDLE}, unit_type=self.ai.worker_type - ) + ).filter(lambda u: u.health_percentage >= min_health_perc) # there is a chance we have no workers if not workers or not target_position: return @@ -389,7 +393,9 @@ def select_worker( # seems there are no townhalls with plenty of resources, don't be fussy at # this point if not townhalls: - return cy_closest_to(target_position, available_workers) + worker = cy_closest_to(target_position, available_workers) + self.remove_worker_from_mineral(worker.tag) + return worker # go through townhalls, we loop through the min fields by distance to # townhall that way there is a good chance we pick a worker at a far mineral @@ -405,26 +411,27 @@ def select_worker( if close_workers := available_workers.filter( lambda w: w.tag in self.mineral_patch_to_list_of_workers[mineral.tag] + and cy_distance_to(w.position, townhall.position) < 10.0 ): worker: Unit = cy_closest_to( target_position, close_workers ) self.remove_worker_from_mineral(worker.tag) return worker - - # try to get a worker at this patch that is not carrying - # resources - if _workers := available_workers.filter( - lambda w: w.tag - in self.mineral_patch_to_list_of_workers[mineral.tag] - and not w.is_carrying_resource - and not w.is_collecting - ): - worker: Unit = _workers.first - # make sure to remove worker, so a new one can be assigned - # to mine - self.remove_worker_from_mineral(worker.tag) - return worker + else: + # try to get a worker at this patch that is not carrying + # resources + if _workers := available_workers.filter( + lambda w: w.tag + in self.mineral_patch_to_list_of_workers[mineral.tag] + and not w.is_carrying_resource + and not w.is_collecting + ): + worker: Unit = _workers.first + # make sure to remove worker, so a new one + # can be assigned to mine + self.remove_worker_from_mineral(worker.tag) + return worker # somehow got here without finding a worker, any worker will do worker: Unit = cy_closest_to(target_position, available_workers)