Skip to content

Commit 9b27727

Browse files
committed
Update expansionlocations again, add dump data, unit.can_be_attacked
1 parent 1dc888e commit 9b27727

File tree

3 files changed

+71
-42
lines changed

3 files changed

+71
-42
lines changed

sc2/bot_ai.py

Lines changed: 23 additions & 22 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
@@ -126,15 +124,24 @@ def known_enemy_structures(self) -> Units:
126124

127125
@property
128126
def main_base_ramp(self) -> "Ramp":
129-
""" Returns the Ramp instance of the closest main-ramp to start location. Look in game_info.py for more information """
127+
""" Returns the Ramp instance of the closest main-ramp to start location.
128+
Look in game_info.py for more information """
130129
if hasattr(self, "cached_main_base_ramp"):
131130
return self.cached_main_base_ramp
132-
""" The reason for len(ramp.upper) in {2, 5} is:
133-
ParaSite map has 5 upper points, and most other maps have 2 upper points at the main ramp. The map Acolyte has 4 upper points at the wrong ramp (which is closest to the start position) """
134-
self.cached_main_base_ramp = min(
135-
(ramp for ramp in self.game_info.map_ramps if len(ramp.upper) in {2, 5}),
136-
key=lambda r: self.start_location.distance_to(r.top_center),
137-
)
131+
# The reason for len(ramp.upper) in {2, 5} is:
132+
# ParaSite map has 5 upper points, and most other maps have 2 upper points at the main ramp.
133+
# The map Acolyte has 4 upper points at the wrong ramp (which is closest to the start position).
134+
try:
135+
self.cached_main_base_ramp = min(
136+
(ramp for ramp in self.game_info.map_ramps if len(ramp.upper) in {2, 5}),
137+
key=lambda r: self.start_location.distance_to(r.top_center),
138+
)
139+
except ValueError:
140+
# Hardcoded hotfix for Honorgrounds LE map, as that map has a large main base ramp with inbase natural
141+
self.cached_main_base_ramp = min(
142+
(ramp for ramp in self.game_info.map_ramps if len(ramp.upper) in {4, 9}),
143+
key=lambda r: self.start_location.distance_to(r.top_center),
144+
)
138145
return self.cached_main_base_ramp
139146

140147
@property_cache_forever
@@ -149,13 +156,9 @@ def expansion_locations(self) -> Dict[Point2, Units]:
149156

150157
# Distance we group resources by
151158
RESOURCE_SPREAD_THRESHOLD = 8.5
152-
minerals = self.state.mineral_field
153159
geysers = self.state.vespene_geyser
154-
all_resources = minerals | geysers
155-
# Presort resources to get faster clustering
156-
all_resources.sort(key=lambda resource: (resource.position.x, resource.position.y))
157160
# Create a group for every resource
158-
resource_groups = [[resource] for resource in all_resources]
161+
resource_groups = [[resource] for resource in self.state.resources]
159162
# Loop the merging process as long as we change something
160163
found_something = True
161164
while found_something:
@@ -177,9 +180,8 @@ def expansion_locations(self) -> Dict[Point2, Units]:
177180
offset_range = 7
178181
offsets = [
179182
(x, y)
180-
for x in range(-offset_range, offset_range + 1)
181-
for y in range(-offset_range, offset_range + 1)
182-
if 4 <= math.hypot(x, y) <= 7
183+
for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2)
184+
if math.hypot(x, y) <= 8
183185
]
184186
# Dict we want to return
185187
centers = {}
@@ -189,20 +191,19 @@ def expansion_locations(self) -> Dict[Point2, Units]:
189191
amount = len(resources)
190192
# Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
191193
# coordinates because bases have size 5.
192-
center_x = round(sum(resource.position.x for resource in resources) / amount) + 0.5
193-
center_y = round(sum(resource.position.y for resource in resources) / amount) + 0.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
194196
possible_points = (Point2((offset[0] + center_x, offset[1] + center_y)) for offset in offsets)
195197
# Filter out points that are too near
196198
possible_points = (
197199
point
198200
for point in possible_points
199201
# Check if point can be built on
200-
if self._game_info.placement_grid[point.rounded] != 0
202+
if self._game_info.placement_grid[point.rounded] == 1
201203
# Check if all resources have enough space to point
202-
and all(point.distance_to(resource) >= (7 if resource in geysers else 6) for resource in resources)
204+
and all(point.distance_to(resource) > (7 if resource in geysers else 6) for resource in resources)
203205
)
204206
# Choose best fitting point
205-
# TODO can we improve this by calculating the distance only one time?
206207
result = min(possible_points, key=lambda point: sum(point.distance_to(resource) for resource in resources))
207208
centers[result] = resources
208209
return centers

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)