Skip to content

Commit fd673a4

Browse files
authored
Merge pull request Dentosal#278 from tweakimp/develop
Update expansionlocations again, add dump data, unit.can_be_attacked
2 parents 6b05f82 + 4837c40 commit fd673a4

File tree

3 files changed

+101
-82
lines changed

3 files changed

+101
-82
lines changed

sc2/bot_ai.py

Lines changed: 53 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from collections import Counter
66
from typing import Any, Dict, List, Optional, Set, Tuple, Union # mypy type checking
77

8-
from s2clientprotocol import common_pb2 as common_pb
9-
108
from .cache import property_cache_forever, property_cache_once_per_frame
119
from .data import ActionResult, Alert, Race, Result, Target, race_gas, race_townhalls, race_worker
1210
from .game_data import AbilityData, GameData
@@ -130,7 +128,7 @@ def main_base_ramp(self) -> "Ramp":
130128
Look in game_info.py for more information """
131129
if hasattr(self, "cached_main_base_ramp"):
132130
return self.cached_main_base_ramp
133-
# The reason for len(ramp.upper) in {2, 5} is:
131+
# The reason for len(ramp.upper) in {2, 5} is:
134132
# ParaSite map has 5 upper points, and most other maps have 2 upper points at the main ramp.
135133
# The map Acolyte has 4 upper points at the wrong ramp (which is closest to the start position).
136134
try:
@@ -157,65 +155,58 @@ def expansion_locations(self) -> Dict[Point2, Units]:
157155
# any resource in a group is closer than 6 to any resource of another group
158156

159157
# Distance we group resources by
160-
from .helpers.devtools import time_this
161-
162-
with time_this("expo locations"):
163-
RESOURCE_SPREAD_THRESHOLD = 8.5
164-
minerals = self.state.mineral_field
165-
geysers = self.state.vespene_geyser
166-
all_resources = minerals | geysers
167-
# Presort resources to get faster clustering
168-
# all_resources.sort(key=lambda resource: resource.position.x ** 2 + resource.position.y ** 2)
169-
all_resources.sort(key=lambda resource: resource.position.x ** 2 + resource.position.y ** 2)
170-
# Create a group for every resource
171-
resource_groups = [[resource] for resource in all_resources]
172-
# Loop the merging process as long as we change something
173-
found_something = True
174-
while found_something:
175-
found_something = False
176-
# Check every combination of two groups
177-
for group_a, group_b in itertools.combinations(resource_groups, 2):
178-
# Check if any pair of resource of these groups is closer than threshold together
179-
if any(
180-
resource_a.distance_to(resource_b) <= RESOURCE_SPREAD_THRESHOLD
181-
for resource_a, resource_b in itertools.product(group_a, group_b)
182-
):
183-
# Remove the single groups and add the merged group
184-
resource_groups.remove(group_a)
185-
resource_groups.remove(group_b)
186-
resource_groups.append(group_a + group_b)
187-
found_something = True
188-
break
189-
# Distance offsets we apply to center of each resource group to find expansion position
190-
offset_range = 7
191-
offsets = [(x, y) for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2)]
192-
# Dict we want to return
193-
centers = {}
194-
# For every resource group:
195-
for resources in resource_groups:
196-
# Possible expansion points
197-
amount = len(resources)
198-
# Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
199-
# coordinates because bases have size 5.
200-
center_x = round(sum(resource.position.x for resource in resources) / amount) + 0.5
201-
center_y = round(sum(resource.position.y for resource in resources) / amount) + 0.5
202-
possible_points = (Point2((offset[0] + center_x, offset[1] + center_y)) for offset in offsets)
203-
# Filter out points that are too near
204-
possible_points = (
205-
point
206-
for point in possible_points
207-
# Check if point can be built on
208-
if self._game_info.placement_grid[point.rounded] == 1
209-
# Check if all resources have enough space to point
210-
and all(point.distance_to(resource) > (7 if resource in geysers else 6) for resource in resources)
211-
)
212-
# Choose best fitting point
213-
# TODO can we improve this by calculating the distance only one time?
214-
result = min(
215-
possible_points, key=lambda point: sum(point.distance_to(resource) for resource in resources)
216-
)
217-
centers[result] = resources
218-
return centers
158+
RESOURCE_SPREAD_THRESHOLD = 8.5
159+
geysers = self.state.vespene_geyser
160+
# Create a group for every resource
161+
resource_groups = [[resource] for resource in self.state.resources]
162+
# Loop the merging process as long as we change something
163+
found_something = True
164+
while found_something:
165+
found_something = False
166+
# Check every combination of two groups
167+
for group_a, group_b in itertools.combinations(resource_groups, 2):
168+
# Check if any pair of resource of these groups is closer than threshold together
169+
if any(
170+
resource_a.distance_to(resource_b) <= RESOURCE_SPREAD_THRESHOLD
171+
for resource_a, resource_b in itertools.product(group_a, group_b)
172+
):
173+
# Remove the single groups and add the merged group
174+
resource_groups.remove(group_a)
175+
resource_groups.remove(group_b)
176+
resource_groups.append(group_a + group_b)
177+
found_something = True
178+
break
179+
# Distance offsets we apply to center of each resource group to find expansion position
180+
offset_range = 7
181+
offsets = [
182+
(x, y)
183+
for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2)
184+
if math.hypot(x, y) <= 8
185+
]
186+
# Dict we want to return
187+
centers = {}
188+
# For every resource group:
189+
for resources in resource_groups:
190+
# Possible expansion points
191+
amount = len(resources)
192+
# Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
193+
# coordinates because bases have size 5.
194+
center_x = int(sum(resource.position.x for resource in resources) / amount) + 0.5
195+
center_y = int(sum(resource.position.y for resource in resources) / amount) + 0.5
196+
possible_points = (Point2((offset[0] + center_x, offset[1] + center_y)) for offset in offsets)
197+
# Filter out points that are too near
198+
possible_points = (
199+
point
200+
for point in possible_points
201+
# Check if point can be built on
202+
if self._game_info.placement_grid[point.rounded] == 1
203+
# Check if all resources have enough space to point
204+
and all(point.distance_to(resource) > (7 if resource in geysers else 6) for resource in resources)
205+
)
206+
# Choose best fitting point
207+
result = min(possible_points, key=lambda point: sum(point.distance_to(resource) for resource in resources))
208+
centers[result] = resources
209+
return centers
219210

220211
def _correct_zerg_supply(self):
221212
""" The client incorrectly rounds zerg supply down instead of up (see

sc2/client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,26 @@ async def get_game_data(self) -> GameData:
138138
)
139139
return GameData(result.data)
140140

141+
async def dump_data(self, ability_id=True, unit_type_id=True, upgrade_id=True, buff_id=True, effect_id=True):
142+
"""
143+
Dump the game data files
144+
choose what data to dump in the keywords
145+
this function writes to a text file
146+
call it one time in on_step with:
147+
await self._client.dump_data()
148+
"""
149+
result = await self._execute(
150+
data=sc_pb.RequestData(
151+
ability_id=ability_id,
152+
unit_type_id=unit_type_id,
153+
upgrade_id=upgrade_id,
154+
buff_id=buff_id,
155+
effect_id=effect_id,
156+
)
157+
)
158+
with open("data_dump.txt", "a") as file:
159+
file.write(str(result.data))
160+
141161
async def get_game_info(self) -> GameInfo:
142162
result = await self._execute(game_info=sc_pb.RequestGameInfo())
143163
return GameInfo(result.game_info)

sc2/unit.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ def __repr__(self) -> str:
4343
return f"UnitOrder({self.ability}, {self.target}, {self.progress})"
4444

4545

46-
class PassengerUnit:
47-
""" Is inherited by the Unit class. Everything in here is also available in Unit. """
48-
46+
class Unit:
4947
def __init__(self, proto_data):
5048
self._proto = proto_data
5149
self.cache = {}
@@ -302,11 +300,6 @@ def energy_percentage(self) -> Union[int, float]:
302300
return 0
303301
return self._proto.energy / self._proto.energy_max
304302

305-
306-
class Unit(PassengerUnit):
307-
308-
# All type data is in PassengerUnit.
309-
310303
@property_immutable_cache
311304
def is_snapshot(self) -> bool:
312305
""" Checks if the unit is only available as a snapshot for the bot.
@@ -396,6 +389,11 @@ def is_revealed(self) -> bool:
396389
""" Checks if the unit is revealed. """
397390
return self._proto.cloak is CloakState.CloakedDetected.value
398391

392+
@property_immutable_cache
393+
def can_be_attacked(self) -> bool:
394+
""" Checks if the unit is revealed or not cloaked and therefore can be attacked """
395+
return self._proto.cloak in {CloakState.NotCloaked.value, CloakState.CloakedDetected.value}
396+
399397
@property_immutable_cache
400398
def buffs(self) -> Set:
401399
""" Returns the set of current buffs the unit has. """
@@ -558,7 +556,8 @@ def is_idle(self) -> bool:
558556
return not self.orders
559557

560558
def is_using_ability(self, abilities: Union[AbilityId, Set[AbilityId]]) -> bool:
561-
""" Check if the unit is using one of the given abilities. """
559+
""" Check if the unit is using one of the given abilities.
560+
Only works for own units. """
562561
if not self.orders:
563562
return False
564563
if isinstance(abilities, AbilityId):
@@ -567,12 +566,14 @@ def is_using_ability(self, abilities: Union[AbilityId, Set[AbilityId]]) -> bool:
567566

568567
@property_immutable_cache
569568
def is_moving(self) -> bool:
570-
""" Checks if the unit is moving. """
569+
""" Checks if the unit is moving.
570+
Only works for own units. """
571571
return self.is_using_ability(AbilityId.MOVE)
572572

573573
@property_immutable_cache
574574
def is_attacking(self) -> bool:
575-
""" Checks if the unit is attacking. """
575+
""" Checks if the unit is attacking.
576+
Only works for own units. """
576577
return self.is_using_ability(
577578
{
578579
AbilityId.ATTACK,
@@ -585,27 +586,32 @@ def is_attacking(self) -> bool:
585586

586587
@property_immutable_cache
587588
def is_patrolling(self) -> bool:
588-
""" Checks if a unit is patrolling. """
589+
""" Checks if a unit is patrolling.
590+
Only works for own units. """
589591
return self.is_using_ability(AbilityId.PATROL)
590592

591593
@property_immutable_cache
592594
def is_gathering(self) -> bool:
593-
""" Checks if a unit is on its way to a mineral field or vespene geyser to mine. """
595+
""" Checks if a unit is on its way to a mineral field or vespene geyser to mine.
596+
Only works for own units. """
594597
return self.is_using_ability(AbilityId.HARVEST_GATHER)
595598

596599
@property_immutable_cache
597600
def is_returning(self) -> bool:
598-
""" Checks if a unit is returning from mineral field or vespene geyser to deliver resources to townhall. """
601+
""" Checks if a unit is returning from mineral field or vespene geyser to deliver resources to townhall.
602+
Only works for own units. """
599603
return self.is_using_ability(AbilityId.HARVEST_RETURN)
600604

601605
@property_immutable_cache
602606
def is_collecting(self) -> bool:
603-
""" Checks if a unit is gathering or returning. """
607+
""" Checks if a unit is gathering or returning.
608+
Only works for own units. """
604609
return self.is_using_ability({AbilityId.HARVEST_GATHER, AbilityId.HARVEST_RETURN})
605610

606611
@property_immutable_cache
607612
def is_constructing_scv(self) -> bool:
608-
""" Checks if the unit is an SCV that is currently building. """
613+
""" Checks if the unit is an SCV that is currently building.
614+
Only works for own units. """
609615
return self.is_using_ability(
610616
{
611617
AbilityId.TERRANBUILD_ARMORY,
@@ -626,12 +632,14 @@ def is_constructing_scv(self) -> bool:
626632

627633
@property_immutable_cache
628634
def is_transforming(self) -> bool:
629-
""" Checks if the unit transforming. """
635+
""" Checks if the unit transforming.
636+
Only works for own units. """
630637
return self.type_id in transforming and self.is_using_ability(transforming[self.type_id])
631638

632639
@property_immutable_cache
633640
def is_repairing(self) -> bool:
634-
""" Checks if the unit is an SCV or MULE that is currently repairing. """
641+
""" Checks if the unit is an SCV or MULE that is currently repairing.
642+
Only works for own units. """
635643
return self.is_using_ability(
636644
{AbilityId.EFFECT_REPAIR, AbilityId.EFFECT_REPAIR_MULE, AbilityId.EFFECT_REPAIR_SCV}
637645
)
@@ -653,9 +661,9 @@ def add_on_land_position(self) -> Point2:
653661
return self.position.offset(Point2((-2.5, 0.5)))
654662

655663
@property_mutable_cache
656-
def passengers(self) -> Set["PassengerUnit"]:
664+
def passengers(self) -> Set["Unit"]:
657665
""" Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """
658-
return {PassengerUnit(unit) for unit in self._proto.passengers}
666+
return {Unit(unit) for unit in self._proto.passengers}
659667

660668
@property_mutable_cache
661669
def passengers_tags(self) -> Set[int]:

0 commit comments

Comments
 (0)