Skip to content

Commit de734f3

Browse files
authored
Merge pull request #176 from BurnySc2/fix_creation_ability_error
2 parents c123c66 + aac5458 commit de734f3

7 files changed

Lines changed: 113 additions & 28 deletions

sc2/bot_ai.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from sc2.bot_ai_internal import BotAIInternal
1414
from sc2.cache import property_cache_once_per_frame
1515
from sc2.constants import (
16+
CREATION_ABILITY_FIX,
1617
EQUIVALENTS_FOR_TECH_PROGRESS,
1718
PROTOSS_TECH_REQUIREMENT,
1819
TERRAN_STRUCTURES_REQUIRE_SCV,
@@ -791,7 +792,7 @@ def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int])
791792
creation_ability: AbilityId = creation_ability_data.exact_id
792793
max_value = max(
793794
[s.build_progress for s in self.structures if s._proto.unit_type in equiv_values] +
794-
[self._abilities_all_units[1].get(creation_ability, 0)],
795+
[self._abilities_count_and_build_progress[1].get(creation_ability, 0)],
795796
default=0,
796797
)
797798
return max_value
@@ -853,12 +854,15 @@ def already_pending(self, unit_type: Union[UpgradeId, UnitTypeId]) -> float:
853854
try:
854855
ability = self.game_data.units[unit_type.value].creation_ability.exact_id
855856
except AttributeError:
856-
# Hotfix for checking pending archons
857-
if unit_type == UnitTypeId.ARCHON:
858-
return self._abilities_all_units[0][AbilityId.ARCHON_WARP_TARGET] / 2
857+
if unit_type in CREATION_ABILITY_FIX:
858+
# Hotfix for checking pending archons
859+
if unit_type == UnitTypeId.ARCHON:
860+
return self._abilities_count_and_build_progress[0][AbilityId.ARCHON_WARP_TARGET] / 2
861+
# Hotfix for rich geysirs
862+
return self._abilities_count_and_build_progress[0][CREATION_ABILITY_FIX[unit_type]]
859863
logger.error(f"Uncaught UnitTypeId: {unit_type}")
860864
return 0
861-
return self._abilities_all_units[0][ability]
865+
return self._abilities_count_and_build_progress[0][ability]
862866

863867
def worker_en_route_to_build(self, unit_type: UnitTypeId) -> float:
864868
"""This function counts how many workers are on the way to start the construction a building.

sc2/bot_ai_internal.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from sc2.cache import property_cache_once_per_frame
2020
from sc2.constants import (
2121
ALL_GAS,
22+
CREATION_ABILITY_FIX,
2223
IS_PLACEHOLDER,
2324
TERRAN_STRUCTURES_REQUIRE_SCV,
2425
FakeEffectID,
@@ -266,7 +267,7 @@ def _correct_zerg_supply(self):
266267

267268
@final
268269
@property_cache_once_per_frame
269-
def _abilities_all_units(self) -> Tuple[CounterType[AbilityId], Dict[AbilityId, float]]:
270+
def _abilities_count_and_build_progress(self) -> Tuple[CounterType[AbilityId], Dict[AbilityId, float]]:
270271
"""Cache for the already_pending function, includes protoss units warping in,
271272
all units in production and all structures, and all morphs"""
272273
abilities_amount: CounterType[AbilityId] = Counter()
@@ -279,10 +280,15 @@ def _abilities_all_units(self) -> Tuple[CounterType[AbilityId], Dict[AbilityId,
279280
if self.race != Race.Terran or not unit.is_structure:
280281
# If an SCV is constructing a building, already_pending would count this structure twice
281282
# (once from the SCV order, and once from "not structure.is_ready")
282-
if unit.type_id == UnitTypeId.ARCHON:
283-
# Hotfix for archons in morph state
284-
creation_ability = AbilityId.ARCHON_WARP_TARGET
285-
abilities_amount[creation_ability] += 2
283+
if unit.type_id in CREATION_ABILITY_FIX:
284+
if unit.type_id == UnitTypeId.ARCHON:
285+
# Hotfix for archons in morph state
286+
creation_ability = AbilityId.ARCHON_WARP_TARGET
287+
abilities_amount[creation_ability] += 2
288+
else:
289+
# Hotfix for rich geysirs
290+
creation_ability = CREATION_ABILITY_FIX[unit.type_id]
291+
abilities_amount[creation_ability] += 1
286292
else:
287293
creation_ability: AbilityId = self.game_data.units[unit.type_id.value].creation_ability.exact_id
288294
abilities_amount[creation_ability] += 1
@@ -743,7 +749,7 @@ async def _issue_vision_events(self):
743749
enemy_units_left_vision: Set[int] = set(self._enemy_units_previous_map) - self.enemy_units.tags
744750
for enemy_unit_tag in enemy_units_left_vision:
745751
await self.on_enemy_unit_left_vision(enemy_unit_tag)
746-
enemy_structures_left_vision: Set[int] = (set(self._enemy_structures_previous_map) - self.enemy_structures.tags)
752+
enemy_structures_left_vision: Set[int] = set(self._enemy_structures_previous_map) - self.enemy_structures.tags
747753
for enemy_structure_tag in enemy_structures_left_vision:
748754
await self.on_enemy_unit_left_vision(enemy_structure_tag)
749755

sc2/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,3 +681,16 @@ def return_NOTAUNIT() -> UnitTypeId:
681681
4: "Point2 or Unit",
682682
5: "Point2 or no target",
683683
}
684+
CREATION_ABILITY_FIX: Dict[UnitTypeId, AbilityId] = {
685+
UnitTypeId.ARCHON: AbilityId.ARCHON_WARP_TARGET,
686+
UnitTypeId.ASSIMILATORRICH: AbilityId.PROTOSSBUILD_ASSIMILATOR,
687+
UnitTypeId.BANELINGCOCOON: AbilityId.MORPHZERGLINGTOBANELING_BANELING,
688+
UnitTypeId.CHANGELING: AbilityId.SPAWNCHANGELING_SPAWNCHANGELING,
689+
UnitTypeId.EXTRACTORRICH: AbilityId.ZERGBUILD_EXTRACTOR,
690+
UnitTypeId.INTERCEPTOR: AbilityId.BUILD_INTERCEPTORS,
691+
UnitTypeId.LURKERMPEGG: AbilityId.MORPH_LURKER,
692+
UnitTypeId.MULE: AbilityId.CALLDOWNMULE_CALLDOWNMULE,
693+
UnitTypeId.RAVAGERCOCOON: AbilityId.MORPHTORAVAGER_RAVAGER,
694+
UnitTypeId.REFINERYRICH: AbilityId.TERRANBUILD_REFINERY,
695+
UnitTypeId.TECHLAB: AbilityId.BUILD_TECHLAB,
696+
}

test/autotest_bot.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def on_step(self, iteration):
7474
async def clean_up_center(self):
7575
map_center = self.game_info.map_center
7676
# Remove everything close to map center
77-
my_units = self.units | self.structures
77+
my_units = self.all_own_units
7878
if my_units:
7979
my_units = my_units.closer_than(20, map_center)
8080
if my_units:
@@ -143,8 +143,8 @@ async def test_game_info_static_variables(self):
143143
assert len(self.game_info.player_races) == 2, self.game_info.player_races
144144
self.tests_done_by_name.add("test_game_info_static_variables")
145145

146-
# Test BotAI action: train SCV
147146
async def test_botai_actions1(self):
147+
# Test BotAI action: train SCV
148148
while self.already_pending(UnitTypeId.SCV) < 1:
149149
if self.can_afford(UnitTypeId.SCV):
150150
self.townhalls.random.train(UnitTypeId.SCV)
@@ -184,8 +184,8 @@ def temp_filter(unit: Unit):
184184
await self._advance_steps(2)
185185
logger.warning("Action test 02 successful.")
186186

187-
# Test BotAI action: move some scvs to the center, some to minerals
188187
async def test_botai_actions3(self):
188+
# Test BotAI action: move some scvs to the center, some to minerals
189189
center = self.game_info.map_center
190190

191191
while self.units.filter(lambda x: x.is_moving).amount < 6 and self.units.gathering.amount >= 6:
@@ -202,8 +202,8 @@ async def test_botai_actions3(self):
202202
await self._advance_steps(2)
203203
logger.warning("Action test 03 successful.")
204204

205-
# Test BotAI action: move all SCVs to mine minerals near townhall
206205
async def test_botai_actions4(self):
206+
# Test BotAI action: move all SCVs to mine minerals near townhall
207207
while self.units.gathering.amount < 12:
208208
mf = self.mineral_field.closest_to(self.townhalls.random)
209209
for scv in self.workers:
@@ -213,8 +213,8 @@ async def test_botai_actions4(self):
213213
await self._advance_steps(2)
214214
logger.warning("Action test 04 successful.")
215215

216-
# Test BotAI action: self.expand_now() which tests for get_next_expansion, select_build_worker, can_place, find_placement, build and can_afford
217216
async def test_botai_actions5(self):
217+
# Test BotAI action: self.expand_now() which tests for get_next_expansion, select_build_worker, can_place, find_placement, build and can_afford
218218
# Wait till worker has started construction of CC
219219
while 1:
220220
if self.can_afford(UnitTypeId.COMMANDCENTER):
@@ -237,8 +237,8 @@ async def test_botai_actions5(self):
237237
await self._advance_steps(2)
238238
logger.warning("Action test 05 successful.")
239239

240-
# Test if reaper grenade shows up in effects
241240
async def test_botai_actions6(self):
241+
# Test if reaper grenade shows up in effects
242242
center = self.game_info.map_center
243243

244244
while 1:
@@ -263,8 +263,8 @@ async def test_botai_actions6(self):
263263
await self._advance_steps(100)
264264
logger.warning("Action test 06 successful.")
265265

266-
# Test ravager effects
267266
async def test_botai_actions7(self):
267+
# Test ravager effects
268268
center = self.game_info.map_center
269269
while 1:
270270
if self.units(UnitTypeId.RAVAGER).amount < 10:
@@ -287,8 +287,8 @@ async def test_botai_actions7(self):
287287
await self._advance_steps(100)
288288
logger.warning("Action test 07 successful.")
289289

290-
# Test if train function works on hatchery, lair, hive
291290
async def test_botai_actions8(self):
291+
# Test if train function works on hatchery, lair, hive
292292
center = self.game_info.map_center
293293
if not self.structures(UnitTypeId.HIVE):
294294
await self.client.debug_create_unit([[UnitTypeId.HIVE, 1, center, 1]])
@@ -319,8 +319,8 @@ async def test_botai_actions8(self):
319319
await self._advance_steps(2)
320320
logger.warning("Action test 08 successful.")
321321

322-
# Morph an archon from 2 high templars
323322
async def test_botai_actions9(self):
323+
# Morph an archon from 2 high templars
324324
center = self.game_info.map_center
325325
await self.client.debug_create_unit(
326326
[
@@ -360,8 +360,8 @@ async def test_botai_actions9(self):
360360
await self._advance_steps(2)
361361
logger.warning("Action test 09 successful.")
362362

363-
# Morph 400 banelings from 400 lings in the same frame
364363
async def test_botai_actions10(self):
364+
# Morph 400 banelings from 400 lings in the same frame
365365
center = self.game_info.map_center
366366

367367
target_amount = 400
@@ -381,8 +381,9 @@ async def test_botai_actions10(self):
381381
# Spawn units
382382
if not bane_nests:
383383
await self.client.debug_create_unit([[UnitTypeId.BANELINGNEST, 1, center, 1]])
384-
if banes.amount + bane_cocoons.amount + lings.amount < target_amount:
385-
await self.client.debug_create_unit([[UnitTypeId.ZERGLING, target_amount - lings.amount, center, 1]])
384+
current_amount = banes.amount + bane_cocoons.amount + lings.amount
385+
if current_amount < target_amount:
386+
await self.client.debug_create_unit([[UnitTypeId.ZERGLING, target_amount - current_amount, center, 1]])
386387

387388
if lings.amount >= target_amount and self.minerals >= 10_000 and self.vespene >= 10_000:
388389
for ling in lings:
@@ -402,8 +403,8 @@ async def test_botai_actions10(self):
402403
await self._advance_steps(2)
403404
logger.warning("Action test 10 successful.")
404405

405-
# Trigger anti armor missile of raven against enemy unit and check if buff was received
406406
async def test_botai_actions11(self):
407+
# Trigger anti armor missile of raven against enemy unit and check if buff was received
407408
await self.clean_up_center()
408409
await self.clean_up_center()
409410

@@ -435,10 +436,8 @@ async def test_botai_actions11(self):
435436
logger.warning("Action test 11 successful.")
436437
await self.clean_up_center()
437438

438-
# Test if structures_without_construction_SCVs works after killing the scv
439439
async def test_botai_actions12(self):
440-
map_center: Point2 = self.game_info.map_center
441-
440+
# Test if structures_without_construction_SCVs works after killing the scv
442441
# Wait till can afford depot
443442
while not self.can_afford(UnitTypeId.SUPPLYDEPOT):
444443
await self.client.debug_all_resources()

test/generate_pickle_files_bot.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def main():
109109
"AcidPlantLE",
110110
"AcolyteLE",
111111
"AcropolisLE",
112+
"AncientCisternAIE",
112113
"Artana",
113114
"AscensiontoAiurLE",
114115
"AutomatonLE",
@@ -132,6 +133,7 @@ def main():
132133
"DefendersLandingLE",
133134
"DigitalFrontier",
134135
"DiscoBloodbathLE",
136+
"DragonScalesAIE",
135137
"DreamcatcherLE",
136138
"EastwatchLE",
137139
"Ephemeron",
@@ -143,12 +145,15 @@ def main():
143145
"FractureLE",
144146
"FrostLE",
145147
"GlitteringAshesAIE",
148+
"GoldenauraAIE",
146149
"GoldenWall506",
147150
"GoldenWallLE",
151+
"GresvanAIE",
148152
"HardwireAIE",
149153
"HonorgroundsLE",
150154
"IceandChrome506",
151155
"IceandChromeLE",
156+
"InfestationStationAIE",
152157
"InsideAndOutAIE",
153158
"InterloperLE",
154159
"JagannathaAIE",
@@ -175,6 +180,7 @@ def main():
175180
"RedshiftLE",
176181
"Reminiscence",
177182
"RomanticideAIE",
183+
"RoyalBloodAIE",
178184
"Sanglune",
179185
"SequencerLE",
180186
"SimulacrumLE",

test/run_example_bots_vs_computer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from sc2.player import Bot, Computer
1919

2020
# Time limit given in seconds of total in game time
21-
game_time_limit_vs_computer = 120
21+
game_time_limit_vs_computer = 240
2222

2323
bot_infos = [
2424
# Protoss

test/test_pickled_data.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import pickle
1414
import random
1515
import sys
16+
import unittest
1617
from contextlib import suppress
1718
from pathlib import Path
1819
from typing import Any, List, Tuple
@@ -24,6 +25,7 @@
2425

2526
from sc2.bot_ai import BotAI
2627
from sc2.client import Client
28+
from sc2.constants import ALL_GAS, CREATION_ABILITY_FIX
2729
from sc2.data import CloakState, Race
2830
from sc2.game_data import AbilityData, Cost, GameData
2931
from sc2.game_info import GameInfo
@@ -919,6 +921,61 @@ def test_units():
919921
assert scvs.by_tag(scvs[0].tag)
920922

921923

924+
def test_exact_creation_ability():
925+
try:
926+
from sc2.dicts.unit_abilities import UNIT_ABILITIES
927+
from sc2.dicts.unit_unit_alias import UNIT_UNIT_ALIAS
928+
except ImportError:
929+
logger.info(f"Import error: dict sc2/dicts/ are missing!")
930+
return
931+
test_case = unittest.TestCase()
932+
bot: BotAI = get_map_specific_bot(random.choice(MAPS))
933+
934+
ignore_types = {
935+
UnitTypeId.ADEPTPHASESHIFT,
936+
UnitTypeId.ARBITERMP,
937+
UnitTypeId.BROODLING,
938+
UnitTypeId.BYPASSARMORDRONE,
939+
UnitTypeId.CORSAIRMP,
940+
UnitTypeId.EGG,
941+
UnitTypeId.ELSECARO_COLONIST_HUT,
942+
UnitTypeId.HERC,
943+
UnitTypeId.HERCPLACEMENT,
944+
UnitTypeId.INFESTEDTERRANSEGG,
945+
UnitTypeId.LARVA,
946+
UnitTypeId.NYDUSCANALCREEPER,
947+
UnitTypeId.QUEENMP,
948+
UnitTypeId.RAVENREPAIRDRONE,
949+
UnitTypeId.REPLICANT,
950+
UnitTypeId.SCOURGEMP,
951+
UnitTypeId.SCOUTMP,
952+
UnitTypeId.WARHOUND,
953+
}
954+
955+
unit_types = list(UNIT_UNIT_ALIAS) + list(UNIT_UNIT_ALIAS.values()) + list(UNIT_ABILITIES) + list(ALL_GAS)
956+
unit_types_unique_sorted = sorted(set(t.name for t in unit_types))
957+
for unit_type_name in unit_types_unique_sorted:
958+
unit_type = UnitTypeId[unit_type_name]
959+
if unit_type in ignore_types:
960+
continue
961+
962+
if unit_type in [
963+
UnitTypeId.ARCHON,
964+
UnitTypeId.ASSIMILATORRICH,
965+
UnitTypeId.EXTRACTORRICH,
966+
UnitTypeId.REFINERYRICH,
967+
]:
968+
with test_case.assertRaises(AttributeError):
969+
_creation_ability = bot.game_data.units[unit_type.value].creation_ability.exact_id
970+
continue
971+
972+
try:
973+
_creation_ability = bot.game_data.units[unit_type.value].creation_ability.exact_id
974+
except AttributeError:
975+
if unit_type not in CREATION_ABILITY_FIX:
976+
assert False, f"Unit type '{unit_type}' missing from CREATION_ABILITY_FIX"
977+
978+
922979
def test_dicts():
923980
# May be missing but that should not fail the tests
924981
try:

0 commit comments

Comments
 (0)