Custom Building Placements
Although ares-sc2 automatically calculates building formations for all base locations,
there are situations where precise placement is critical, and custom-building layouts
are preferred. To address this, ares-sc2 allows users to specify custom-building positions,
which are seamlessly integrated into its placement calculations. These custom placements
are fully compatible with core ares-sc2
features, such as the Build Runner, BuildStructure
behavior, and direct interactions with the building tracker via the ManagerMediator
.
Additionally, the system ensures that standard placements within a base location adapt
to account for user-defined custom positions.
At present, custom placements are supported exclusively for Protoss vs. Zerg natural wall setups. Support for additional scenarios will be introduced in future updates.
Defining custom placements¶
Create a file in the root of your bot folder names building_placements.yml
, you should enter placements
into this file like below.
Protoss:
AbyssalReef:
VsZergNatWall:
AvailableVsRaces: ["Zerg", "Random"]
UpperSpawn:
FirstPylon: [[64., 105.]]
Pylons: [[63., 112.]]
ThreeByThrees: [[68.5, 109.5], [66.5, 106.5], [60.5, 106.5]]
StaticDefences: [[64., 110.]]
GateKeeper: [[62.25, 105.86]]
LowerSpawn:
FirstPylon: [[136., 39.]]
Pylons: [[137., 32.]]
ThreeByThrees: [[131.5, 34.5], [133.5, 37.5], [139.5, 37.5]]
StaticDefences: [[136., 36.]]
GateKeeper: [[137.25, 38.6]]
Acropolis:
VsZergNatWall:
AvailableVsRaces: ["Zerg", "Random"]
UpperSpawn:
FirstPylon: [ [ 35., 109. ] ]
Pylons: [ [ 32., 109. ] ]
ThreeByThrees: [ [ 38.5, 106.5 ], [ 34.5, 105.5 ], [ 31.5, 105.5 ] ]
StaticDefences: [ [ 39., 109. ] ]
GateKeeper: [ [ 36.6, 105.6 ] ]
LowerSpawn:
FirstPylon: [ [ 141., 63. ] ]
Pylons: [ [ 144., 63. ] ]
ThreeByThrees: [ [ 137.5, 66.5 ], [ 141.5, 66.5 ], [ 144.5, 66.5 ] ]
StaticDefences: [ [ 137., 63. ] ]
GateKeeper: [ [ 139.3, 67.4 ] ]
Automaton:
VsZergNatWall:
AvailableVsRaces: ["Zerg", "Random"]
UpperSpawn:
FirstPylon: [ [ 141., 139. ] ]
Pylons: [ [ 140., 142. ] ]
ThreeByThrees: [ [ 138.5, 133.5 ], [ 136.5, 137.5 ], [ 136.5, 140.5 ] ]
StaticDefences: [ [ 142., 136. ] ]
GateKeeper: [ [ 136.9, 135.6 ] ]
LowerSpawn:
FirstPylon: [ [ 43., 42. ] ]
Pylons: [ [ 44., 39. ] ]
ThreeByThrees: [ [ 45.5, 46.5 ], [ 47.5, 42.5 ], [ 47.5, 39.5 ] ]
StaticDefences: [ [ 42., 45. ] ]
GateKeeper: [ [ 47.15, 45.3 ] ]
Ephemeron:
VsZergNatWall:
AvailableVsRaces: ["Zerg", "Random"]
UpperSpawn:
FirstPylon: [ [ 37., 112. ] ]
Pylons: [ [ 37., 109. ] ]
ThreeByThrees: [ [ 42.5, 114.5 ], [ 42.5, 110.5 ], [ 41.5, 107.5 ] ]
StaticDefences: [ [ 38., 115. ] ]
GateKeeper: [ [ 43.1, 112.58 ] ]
LowerSpawn:
FirstPylon: [ [ 125., 49. ] ]
Pylons: [ [ 125., 52. ] ]
ThreeByThrees: [ [ 120.5, 45.5 ], [ 120.5, 49.5 ], [ 120.5, 52.5 ] ]
StaticDefences: [ [ 125., 46. ] ]
GateKeeper: [ [ 119.7, 47.4 ] ]
Interloper:
VsZergNatWall:
AvailableVsRaces: ["Zerg", "Random"]
UpperSpawn:
FirstPylon: [ [ 31., 112. ] ]
Pylons: [ [ 31., 109. ] ]
ThreeByThrees: [ [ 35.5, 114.5 ], [ 35.5, 110.5 ], [ 35.5, 107.5 ] ]
StaticDefences: [ [ 31., 115. ] ]
GateKeeper: [ [ 36.16, 112.68 ] ]
LowerSpawn:
FirstPylon: [ [ 121., 56. ] ]
Pylons: [ [ 121., 59. ] ]
ThreeByThrees: [ [ 116.5, 53.5 ], [ 116.5, 57.5 ], [ 116.5, 60.5 ] ]
StaticDefences: [ [ 121., 53. ] ]
GateKeeper: [ [ 115.78, 55.75 ] ]
Thunderbird:
VsZergNatWall:
AvailableVsRaces: ["Zerg", "Random"]
UpperSpawn:
FirstPylon: [ [ 46., 106. ] ]
Pylons: [ [ 46., 103. ] ]
ThreeByThrees: [ [ 50.5, 109.5 ], [ 50.5, 105.5 ], [ 50.5, 102.5 ] ]
StaticDefences: [ [ 46., 109. ] ]
GateKeeper: [ [ 51.18, 107.67 ] ]
LowerSpawn:
FirstPylon: [ [ 144., 51 ] ]
Pylons: [ [ 144., 54. ] ]
ThreeByThrees: [ [ 139.5, 46.5 ], [ 139.5, 50.5 ], [ 139.5, 53.5 ] ]
StaticDefences: [ [ 144., 48. ] ]
GateKeeper: [ [ 138.64, 48.49 ] ]
The values shown above are the default settings in ares, so there's no need to create your own file if you're satisfied with them. However, you can customize these settings by creating your own building_placements.yml file and specifying only the elements you wish to change. ares-sc2 will automatically prioritize your custom placements and fill in any missing elements with the default values.
This is an example contents of a building_placements.yml
file where the first pylon position on Thunderbird is tweaked.
Thunderbird:
VsZergNatWall:
UpperSpawn:
FirstPylon: [ [ 47., 107. ] ]
LowerSpawn:
FirstPylon: [ [ 143., 52 ] ]
When creating your building placements file, ensure the keys are spelled correctly and match the example
above. Internally ares-sc2
checks an Enum
similar to this when parsing the file:
class BuildingPlacementOptions(str, Enum):
LOWER_SPAWN = "LowerSpawn"
UPPER_SPAWN = "UpperSpawn"
VS_ZERG_NAT_WALL = "VsZergNatWall"
FIRST_PYLON = "FirstPylon"
PYLONS = "Pylons"
THREE_BY_THREES = "ThreeByThrees"
STATIC_DEFENCES = "StaticDefences"
GATE_KEEPER = "GateKeeper"
Providing impossible placements¶
ares-sc2
validates your placements before adding them internally. If an invalid placement is detected,
an error message will be logged, but your bot will continue running as normal. If your placements
aren’t working as expected, be sure to check the logs for more details.
Retrieve the gate keeper placement¶
In Protoss vs Zerg this is the gap in the natural wall that is usually blocked by a
gateway unit. Keep in mind this could be None
if no position is provided for the
current map.
Using custom placements with ares¶
There are several ways these placements can be utilized.
Via the BuildRunner¶
See build Runner tutorial if you're unfamiliar.
Below is an example of a valid build order that places structures at the natural wall.
To specify that a structure should use your custom natural wall placements, simply
add @ nat_wall
when declaring a build step. If the map has no custom placements or
all available positions are already taken, the build runner will automatically find a
suitable alternative nearby.
UseData: True
# How should we choose a build? Cycle is the only option for now
BuildSelection: Cycle
# For each Race / Opponent ID choose a build selection
BuildChoices:
# test_123 is active if Debug: True (set via a `config.yml` file)
test_123:
BotName: Test
Cycle:
- NatWall
Protoss:
BotName: ProtossRace
Cycle:
- NatWall
Random:
BotName: RandomRace
Cycle:
- NatWall
Terran:
BotName: TerranRace
Cycle:
- NatWall
Zerg:
BotName: ZergRace
Cycle:
- NatWall
Builds:
NatWall:
ConstantWorkerProductionTill: 44
AutoSupplyAtSupply: 23
OpeningBuildOrder:
- 14 pylon @ nat_wall
- 15 gate @ nat_wall
- 16 gate @ nat_wall
- 16 core @ nat_wall
- 16 pylon @ nat_wall
- 16 shieldbattery @ nat_wall
BuildStructure
behavior¶
You can build wall structures within your own bot logic via the
BuildStructure
behavior.
If wall placements are not available this will look for a closely alternative. Ensure
base_location=self.mediator.get_own_nat
to ensure natural wall is found.
See example code:
from sc2.ids.unit_typeid import UnitTypeId
self.register_behavior(
BuildStructure(
base_location=self.mediator.get_own_nat,
structure_id=UnitTypeId.GATEWAY,
wall=True,
to_count_per_base=2
)
)
self.register_behavior(
BuildStructure(
base_location=self.mediator.get_own_nat,
structure_id=UnitTypeId.CYBERNETICSCORE,
wall=True,
to_count_per_base=1
)
)
self.register_behavior(
BuildStructure(
base_location=self.mediator.get_own_nat,
structure_id=UnitTypeId.PYLON,
wall=True,
to_count_per_base=2
)
)
self.register_behavior(
BuildStructure(
base_location=self.mediator.get_own_nat,
structure_id=UnitTypeId.SHIELDBATTERY,
wall=True,
to_count_per_base=1
)
)
Via the ManagerMediator
¶
For more customized control you can interact with the wall placements via the mediator
. See
some examples below.
Get the first pylon placement without reserving placement in the building tracker:
from sc2.ids.unit_typeid import UnitTypeId
if placement := mediator.request_building_placement(
base_location=self.mediator.get_own_nat,
structure_type=UnitTypeId.PYLON,
first_pylon=self.first_pylon,
reserve_placement=False
):
pass
Work directly with the raw data, example here gets the natural wall placements.
from ares.consts import BuildingSize
from sc2.position import Point2
placements_dict: dict[Point2, dict[BuildingSize, dict]] = self.mediator.get_placements_dict
natural_placements: dict[BuildingSize, dict] = placements_dict[self.mediator.get_own_nat]
two_by_twos_at_wall: list[Point2] = [
placement
for placement in natural_placements[BuildingSize.TWO_BY_TWO]
if natural_placements[BuildingSize.TWO_BY_TWO][placement]["is_wall"]
]
three_by_threes_at_wall: list[Point2] = [
placement
for placement in natural_placements[BuildingSize.TWO_BY_TWO]
if natural_placements[BuildingSize.THREE_BY_THREE][placement]["is_wall"]
]