Skip to content

UnitManager

Classes

UnitManager

Bases: AoE2Object

Manager of everything unit related.

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
class UnitManager(AoE2Object):
    """Manager of everything unit related."""

    _link_list = [
        RetrieverObjectLink("_player_units", "Units", "players_units", process_as_object=PlayerUnits),
        RetrieverObjectLink("next_unit_id", "DataHeader", "next_unit_id_to_place")
    ]

    def __init__(
            self,
            _player_units: List[PlayerUnits],
            next_unit_id: int,
            **kwargs
    ):
        super().__init__(**kwargs)

        self.units = [pu.units for pu in _player_units]
        self.reference_id_generator = create_id_generator(next_unit_id)

    @property
    def next_unit_id(self):
        return self.get_new_reference_id()

    @property
    def units(self):
        return self._units

    @units.setter
    def units(self, value: List[List[Unit]]):
        def _raise():
            raise ValueError("Units should be list with a maximum of 9 sub lists, example: [[Unit], [Unit, Unit], ...]")

        if len(value) > 9:
            _raise()
        elif len(value) < 9:
            value.extend([[] for _ in range(9 - len(value))])

        self._units = UuidList(self._uuid, value)

    def update_unit_player_values(self):
        """Function to update all player values in all units. Useful when units are moved manually (in mass)."""
        for player in PlayerId.all():
            for unit in self.units[player]:
                unit._player = player

    def clone_unit(
            self,
            unit: Unit,
            player: int | PlayerId = None,
            unit_const: int = None,
            x: float = None,
            y: float = None,
            z: float = None,
            rotation: float = None,
            garrisoned_in_id: int = None,
            animation_frame: int = None,
            status: int = None,
            reference_id: int = None,
            tile: Tile | Tuple[int, int] = None,
    ) -> Unit:
        """
        Clones an existing unit with the adjusted variables. Everything except the initial unit is optional.
        When arguments are provided, they will override the corresponding values in the cloned unit.

        Args:
            unit: The unit to clone
            player: The player to set the cloned unit to (If not provided, the original player will be used)
            unit_const: The unit you're placing (If not provided, the original unit constant will be used)
            x: The X coordinate of the cloned unit (If not provided, the original x coordinate will be used)
            y: The Y coordinate of the cloned unit (If not provided, the original y coordinate will be used)
            z: The Z coordinate of the cloned unit (If not provided, the original z coordinate will be used)
            rotation: The rotation of the cloned unit (If not provided, the original rotation will be used)
            garrisoned_in_id: The id of the garrisoned unit (If not provided, the original garrisoned id will be used)
            animation_frame: The animation frame of the cloned unit (If not provided, the original animation frame will be used)
            status: The status of the cloned unit (If not provided, the original status will be used)
            reference_id: Reference id of the cloned unit (If not provided, a new reference id will be generated)
            tile: The tile of the cloned unit (If not provided, the original x,y coordinates will be used)

        Returns:
            The cloned unit
        """

        if (x is not None or y is not None) and tile is not None:
            raise ValueError("Cannot use both x,y notation and tile notation at the same time")

        return self.add_unit(
            player=player or unit.player,
            unit_const=unit_const or unit.unit_const,
            x=x or unit.x,
            y=y or unit.y,
            z=z or unit.z,
            rotation=rotation or unit.rotation,
            garrisoned_in_id=garrisoned_in_id or unit.garrisoned_in_id,
            animation_frame=animation_frame or unit.initial_animation_frame,
            status=status or unit.status,
            reference_id=reference_id,
            tile=tile,
        )

    def add_unit(
            self,
            player: int | PlayerId,
            unit_const: int,
            x: float = 0,
            y: float = 0,
            z: float = 0,
            rotation: float = 0,
            garrisoned_in_id: int = -1,
            animation_frame: int = 0,
            status: int = 2,
            reference_id: int = None,
            tile: Tile | Tuple[int, int] = None,
    ) -> Unit:
        """
        Adds a unit to the scenario.

        Args:
            player: The player the unit belongs to.
            unit_const: Defines what unit you're placing. The IDs used in the unit/buildings dataset.
            x: The x location in the scenario.
            y: The y location in the scenario.
            z: The z (height) location in the scenario.
            rotation: The rotation of the unit.
            garrisoned_in_id: The reference_id of another unit this unit is garrisoned in.
            animation_frame: The animation frame of the unit.
            status: Unknown - Always 2. 0-6 no difference (?) | 7-255 makes it disappear. (Except from the mini-map)
            reference_id: The reference ID of this unit. Normally added automatically. Used for garrisoning or reference
                in triggers
            tile: An object that represents a tile on the map. Replaces parameters x and y. Also, automatically adds
                .5 to both ints to place the unit centered on the tile.

        Returns:
            The Unit created
        """
        if reference_id is None:
            reference_id = self.get_new_reference_id()

        unit = Unit(
            player=player,
            x=x if tile is None else (tile[0] + .5),
            y=y if tile is None else (tile[1] + .5),
            z=z,
            reference_id=reference_id,
            unit_const=unit_const,
            status=status,
            rotation=rotation,
            initial_animation_frame=animation_frame,
            garrisoned_in_id=garrisoned_in_id,
            uuid=self._uuid
        )

        self.units[player].append(unit)
        return unit

    def get_player_units(self, player: int | PlayerId) -> List[Unit]:
        """
        Returns a list of UnitObjects for the given player.

        Raises:
            ValueError: If player is not between 0 (GAIA) and 8 (EIGHT)
        """
        if not 0 <= player <= 8:
            raise ValueError("Player must have a value between 0 and 8")
        return self.units[player]

    def get_all_units(self) -> List[Unit]:
        units = []
        for player_units in self.units:
            units += player_units
        return units

    def filter_units_by(
            self,
            attr: str,
            unit_attrs: List[int],
            blacklist: bool = False,
            player_list: List[Union[int, PlayerId]] = None,
            unit_list: List[Unit] = None
    ) -> List[Unit]:
        """
        Filter units based on a given attribute of units

        Args:
            attr: The attribute to filter by
            unit_attrs: The values for the attributes to filter with
            blacklist: Use the given constant list as blacklist instead of whitelist
            player_list: A list of players to filter from. If not used, all players are used.
            unit_list: A set of units to filter from. If not used, all units are used.

        Returns:
            A list of units

        Raises:
            AttributeError: If the provided attr does not exist on objects of the Unit class
        """

        if unit_list is None:
            unit_list = self.get_all_units()
        if player_list is not None:
            unit_list = [unit for unit in unit_list if unit.player in player_list]

        if len(unit_list) == 0:
            return []

        unit = unit_list[0]
        if not hasattr(unit, attr):
            raise AttributeError(f"Cannot filter Unit objects by {attr}")

        # Both return statements can be combined using: ((unit.unit_const in unit_consts) != blacklist)
        # But splitting them helps performance (not checking against blacklist for each entry)
        if not blacklist:
            return [unit for unit_attr in unit_attrs for unit in unit_list if getattr(unit, attr) == unit_attr]
        return [unit for unit_attr in unit_attrs for unit in unit_list if getattr(unit, attr) != unit_attr]

    def filter_units_by_const(
            self,
            unit_consts: List[int],
            blacklist: bool = False,
            player_list: List[Union[int, PlayerId]] = None,
            unit_list: List[Unit] = None
    ) -> List[Unit]:
        """
        Filter unit on their unit_const value.

        Args:
            unit_consts: The constants to filter with
            blacklist: Use the given constant list as blacklist instead of whitelist
            player_list: A list of players to filter from. If not used, all players are used.
            unit_list: A set of units to filter from. If not used, all units are used.

        Returns:
            A list of units
        """
        return self.filter_units_by("unit_const", unit_consts, blacklist, player_list, unit_list)

    def filter_units_by_reference_id(
            self,
            unit_reference_ids: List[int],
            blacklist: bool = False,
            player_list: List[Union[int, PlayerId]] = None,
            unit_list: List[Unit] = None
    ) -> List[Unit]:
        """
        Filter unit on their unit_const value.

        Args:
            unit_reference_ids (List[int]): The reference_ids to filter with
            blacklist (bool): Use the given constant list as blacklist instead of whitelist
            player_list (List[int]): A list of players to filter from. If not used, all players are used.
            unit_list (List[Unit]): A set of units to filter from. If not used, all units are used.

        Returns:
            A list of units
        """
        return self.filter_units_by("reference_id", unit_reference_ids, blacklist, player_list, unit_list)

    def get_units_in_area(
            self,
            x1: float = None,
            y1: float = None,
            x2: float = None,
            y2: float = None,
            tile1: Tile = None,
            tile2: Tile = None,
            unit_list: List[Unit] = None,
            players: List[Union[int, PlayerId]] = None,
            ignore_players: List[PlayerId] = None
    ) -> List[Unit]:
        """
        Returns all units in the square with left corner (x1, y1) and right corner (x2, y2). Both corners inclusive.

        Args:
            x1: The X location of the left corner
            y1: The Y location of the left corner
            x2: The X location of the right corner
            y2: The Y location of the right corner
            tile1: The x,y location of the 1st corner as Tile Object
            tile2: The x,y location of the 2nd corner as Tile Object
            unit_list: (Optional) A list of units (Defaults to all units in the map, including GAIA (Trees etc.)
            players: (Optional) A list of Players which units need to be selected from the selected area
            ignore_players: (Optional) A list of Players which units need to be ignored from the selected area

        Raises:
            ValueError: if not all 4 (x1, y1, x2 and y2) are used simultaneously.
                Or if both (tile1 and tile2) are not used simultaneously.
                Or if any of the 4 (x1, y1, x2, y2) is used together with any of (tile1, tile2). Use one or the other.
                Or if players and ignore_players are used simultaneously.
        """
        if (x1 is not None or y1 is not None or x2 is not None or y2 is not None) and any([tile1, tile2]):
            raise ValueError("Cannot use both x1,y1,x2,y2 notation and tile1,tile2 notation at the same time")
        if (x1 is not None or y1 is not None or x2 is not None or y2 is not None) and \
                (x1 is None or y1 is None or x2 is None or y2 is None):
            raise ValueError("Cannot use some but not all from x1,y1,x2,y2.")
        if (not all([tile1, tile2])) and any([tile1, tile2]):
            raise ValueError("Cannot use one from tile1, tile2. Use both.")
        if players is not None and ignore_players is not None:
            raise ValueError("Cannot use both whitelist (players) and blacklist (ignore_players) at the same time")

        if tile1:
            x1 = tile1.x
            y1 = tile1.y
            x2 = tile2.x
            y2 = tile2.y
        else:
            # Inclusive selection
            x2 += 1
            y2 += 1

        if players is not None:
            players = players
        elif ignore_players is not None:
            players = [p for p in PlayerId if p not in ignore_players]
        else:
            players = [p for p in PlayerId]

        if unit_list is None:
            unit_list = self.get_all_units()

        return [unit for unit in unit_list if x1 <= unit.x <= x2 and y1 <= unit.y <= y2 and unit.player in players]

    @staticmethod
    def change_ownership(unit: Unit | List[Unit], to_player: int | PlayerId) -> None:
        """
        Changes a unit's ownership to the given player.

        Args:
            unit: The unit object which ownership will be changed
            to_player: The player that'll get ownership over the unit (using PlayerId enum)
        """
        if isinstance(unit, list):
            for u in unit:
                u.player = to_player
        else:
            unit.player = to_player

    def get_new_reference_id(self) -> int:
        """
        Get a new ID each time the function is called. Starting from the current highest ID.

        Returns:
            The newly generated ID
        """
        return next(self.reference_id_generator)

    def find_highest_reference_id(self) -> int:
        """
        Find the highest ID in the map. Searches through all units for the highest ID.

        Returns:
            The highest ID in the map
        """
        highest_id = 0  # If no units, default to 0
        for player in PlayerId.all():
            for unit in self.units[player]:
                highest_id = max(highest_id, unit.reference_id)
        return highest_id

    def remove_unit(self, reference_id: int = None, unit: Unit = None) -> None:
        """
        Removes a unit. Please note that `unit=...` is a lot faster than `reference_id=...` due to reference_id having
        to search through all units on the map. And unit has an ownership (player) attribute which is used for knowing
        which list to remove the unit from.

        Args:
            reference_id: The id of the unit. Note that this is NOT a unit constant (So NOT: UnitInfo.ARCHER)
            unit: The Unit object to be removed.
        """
        if reference_id is not None and unit is not None:
            raise ValueError("Cannot use both unit_ref_id and unit arguments. Use one or the other.")
        if reference_id is None and unit is None:
            raise ValueError("Both unit_ref_id and unit arguments were unused. Use one.")

        if reference_id is not None:
            for player in range(0, 9):
                for i, unit in enumerate(self.units[player]):
                    if unit.reference_id == reference_id:
                        del self.units[player][i]
                        return
        elif unit is not None:
            self.units[unit.player].remove(unit)

    def remove_eye_candy(self) -> None:
        eye_candy_ids = [1351, 1352, 1353, 1354, 1355, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366]
        self.units[0] = [gaia_unit for gaia_unit in self.units[0] if gaia_unit.unit_const not in eye_candy_ids]

    # ###############################################################################################
    # ################################# Functions for reconstruction ################################
    # ###############################################################################################

    @property
    def _player_units(self):
        player_units = []
        for i in range(9):
            units = self.get_player_units(i)
            player_units.append(PlayerUnits(unit_count=len(units), units=units))

        return UuidList(self._uuid, player_units)

Attributes

Attribute Type
reference_id_generator instance-attribute
next_unit_id property
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
34
35
def next_unit_id(self):
    return self.get_new_reference_id()
units property writable
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
38
39
def units(self):
    return self._units

Functions


def __init__(...)

Parameters:

Name Type Description Default
_player_units List[PlayerUnits] - required
next_unit_id int - required
kwargs ? - {}
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
22
23
24
25
26
27
28
29
30
31
def __init__(
        self,
        _player_units: List[PlayerUnits],
        next_unit_id: int,
        **kwargs
):
    super().__init__(**kwargs)

    self.units = [pu.units for pu in _player_units]
    self.reference_id_generator = create_id_generator(next_unit_id)

def add_unit(...)

Adds a unit to the scenario.

Parameters:

Name Type Description Default
player int | PlayerId

The player the unit belongs to.

required
unit_const int

Defines what unit you're placing. The IDs used in the unit/buildings dataset.

required
x float

The x location in the scenario.

0
y float

The y location in the scenario.

0
z float

The z (height) location in the scenario.

0
rotation float

The rotation of the unit.

0
garrisoned_in_id int

The reference_id of another unit this unit is garrisoned in.

-1
animation_frame int

The animation frame of the unit.

0
status int

Unknown - Always 2. 0-6 no difference (?) | 7-255 makes it disappear. (Except from the mini-map)

2
reference_id int

The reference ID of this unit. Normally added automatically. Used for garrisoning or reference in triggers

None
tile Tile | Tuple[int, int]

An object that represents a tile on the map. Replaces parameters x and y. Also, automatically adds .5 to both ints to place the unit centered on the tile.

None

Returns:

Type Description
Unit

The Unit created

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def add_unit(
        self,
        player: int | PlayerId,
        unit_const: int,
        x: float = 0,
        y: float = 0,
        z: float = 0,
        rotation: float = 0,
        garrisoned_in_id: int = -1,
        animation_frame: int = 0,
        status: int = 2,
        reference_id: int = None,
        tile: Tile | Tuple[int, int] = None,
) -> Unit:
    """
    Adds a unit to the scenario.

    Args:
        player: The player the unit belongs to.
        unit_const: Defines what unit you're placing. The IDs used in the unit/buildings dataset.
        x: The x location in the scenario.
        y: The y location in the scenario.
        z: The z (height) location in the scenario.
        rotation: The rotation of the unit.
        garrisoned_in_id: The reference_id of another unit this unit is garrisoned in.
        animation_frame: The animation frame of the unit.
        status: Unknown - Always 2. 0-6 no difference (?) | 7-255 makes it disappear. (Except from the mini-map)
        reference_id: The reference ID of this unit. Normally added automatically. Used for garrisoning or reference
            in triggers
        tile: An object that represents a tile on the map. Replaces parameters x and y. Also, automatically adds
            .5 to both ints to place the unit centered on the tile.

    Returns:
        The Unit created
    """
    if reference_id is None:
        reference_id = self.get_new_reference_id()

    unit = Unit(
        player=player,
        x=x if tile is None else (tile[0] + .5),
        y=y if tile is None else (tile[1] + .5),
        z=z,
        reference_id=reference_id,
        unit_const=unit_const,
        status=status,
        rotation=rotation,
        initial_animation_frame=animation_frame,
        garrisoned_in_id=garrisoned_in_id,
        uuid=self._uuid
    )

    self.units[player].append(unit)
    return unit

def change_ownership(...) staticmethod

Changes a unit's ownership to the given player.

Parameters:

Name Type Description Default
unit Unit | List[Unit]

The unit object which ownership will be changed

required
to_player int | PlayerId

The player that'll get ownership over the unit (using PlayerId enum)

required
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
@staticmethod
def change_ownership(unit: Unit | List[Unit], to_player: int | PlayerId) -> None:
    """
    Changes a unit's ownership to the given player.

    Args:
        unit: The unit object which ownership will be changed
        to_player: The player that'll get ownership over the unit (using PlayerId enum)
    """
    if isinstance(unit, list):
        for u in unit:
            u.player = to_player
    else:
        unit.player = to_player

def clone_unit(...)

Clones an existing unit with the adjusted variables. Everything except the initial unit is optional. When arguments are provided, they will override the corresponding values in the cloned unit.

Parameters:

Name Type Description Default
unit Unit

The unit to clone

required
player int | PlayerId

The player to set the cloned unit to (If not provided, the original player will be used)

None
unit_const int

The unit you're placing (If not provided, the original unit constant will be used)

None
x float

The X coordinate of the cloned unit (If not provided, the original x coordinate will be used)

None
y float

The Y coordinate of the cloned unit (If not provided, the original y coordinate will be used)

None
z float

The Z coordinate of the cloned unit (If not provided, the original z coordinate will be used)

None
rotation float

The rotation of the cloned unit (If not provided, the original rotation will be used)

None
garrisoned_in_id int

The id of the garrisoned unit (If not provided, the original garrisoned id will be used)

None
animation_frame int

The animation frame of the cloned unit (If not provided, the original animation frame will be used)

None
status int

The status of the cloned unit (If not provided, the original status will be used)

None
reference_id int

Reference id of the cloned unit (If not provided, a new reference id will be generated)

None
tile Tile | Tuple[int, int]

The tile of the cloned unit (If not provided, the original x,y coordinates will be used)

None

Returns:

Type Description
Unit

The cloned unit

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def clone_unit(
        self,
        unit: Unit,
        player: int | PlayerId = None,
        unit_const: int = None,
        x: float = None,
        y: float = None,
        z: float = None,
        rotation: float = None,
        garrisoned_in_id: int = None,
        animation_frame: int = None,
        status: int = None,
        reference_id: int = None,
        tile: Tile | Tuple[int, int] = None,
) -> Unit:
    """
    Clones an existing unit with the adjusted variables. Everything except the initial unit is optional.
    When arguments are provided, they will override the corresponding values in the cloned unit.

    Args:
        unit: The unit to clone
        player: The player to set the cloned unit to (If not provided, the original player will be used)
        unit_const: The unit you're placing (If not provided, the original unit constant will be used)
        x: The X coordinate of the cloned unit (If not provided, the original x coordinate will be used)
        y: The Y coordinate of the cloned unit (If not provided, the original y coordinate will be used)
        z: The Z coordinate of the cloned unit (If not provided, the original z coordinate will be used)
        rotation: The rotation of the cloned unit (If not provided, the original rotation will be used)
        garrisoned_in_id: The id of the garrisoned unit (If not provided, the original garrisoned id will be used)
        animation_frame: The animation frame of the cloned unit (If not provided, the original animation frame will be used)
        status: The status of the cloned unit (If not provided, the original status will be used)
        reference_id: Reference id of the cloned unit (If not provided, a new reference id will be generated)
        tile: The tile of the cloned unit (If not provided, the original x,y coordinates will be used)

    Returns:
        The cloned unit
    """

    if (x is not None or y is not None) and tile is not None:
        raise ValueError("Cannot use both x,y notation and tile notation at the same time")

    return self.add_unit(
        player=player or unit.player,
        unit_const=unit_const or unit.unit_const,
        x=x or unit.x,
        y=y or unit.y,
        z=z or unit.z,
        rotation=rotation or unit.rotation,
        garrisoned_in_id=garrisoned_in_id or unit.garrisoned_in_id,
        animation_frame=animation_frame or unit.initial_animation_frame,
        status=status or unit.status,
        reference_id=reference_id,
        tile=tile,
    )

def filter_units_by(...)

Filter units based on a given attribute of units

Parameters:

Name Type Description Default
attr str

The attribute to filter by

required
unit_attrs List[int]

The values for the attributes to filter with

required
blacklist bool

Use the given constant list as blacklist instead of whitelist

False
player_list List[Union[int, PlayerId]]

A list of players to filter from. If not used, all players are used.

None
unit_list List[Unit]

A set of units to filter from. If not used, all units are used.

None

Returns:

Type Description
List[Unit]

A list of units

Raises:

Type Description
AttributeError

If the provided attr does not exist on objects of the Unit class

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def filter_units_by(
        self,
        attr: str,
        unit_attrs: List[int],
        blacklist: bool = False,
        player_list: List[Union[int, PlayerId]] = None,
        unit_list: List[Unit] = None
) -> List[Unit]:
    """
    Filter units based on a given attribute of units

    Args:
        attr: The attribute to filter by
        unit_attrs: The values for the attributes to filter with
        blacklist: Use the given constant list as blacklist instead of whitelist
        player_list: A list of players to filter from. If not used, all players are used.
        unit_list: A set of units to filter from. If not used, all units are used.

    Returns:
        A list of units

    Raises:
        AttributeError: If the provided attr does not exist on objects of the Unit class
    """

    if unit_list is None:
        unit_list = self.get_all_units()
    if player_list is not None:
        unit_list = [unit for unit in unit_list if unit.player in player_list]

    if len(unit_list) == 0:
        return []

    unit = unit_list[0]
    if not hasattr(unit, attr):
        raise AttributeError(f"Cannot filter Unit objects by {attr}")

    # Both return statements can be combined using: ((unit.unit_const in unit_consts) != blacklist)
    # But splitting them helps performance (not checking against blacklist for each entry)
    if not blacklist:
        return [unit for unit_attr in unit_attrs for unit in unit_list if getattr(unit, attr) == unit_attr]
    return [unit for unit_attr in unit_attrs for unit in unit_list if getattr(unit, attr) != unit_attr]

def filter_units_by_const(...)

Filter unit on their unit_const value.

Parameters:

Name Type Description Default
unit_consts List[int]

The constants to filter with

required
blacklist bool

Use the given constant list as blacklist instead of whitelist

False
player_list List[Union[int, PlayerId]]

A list of players to filter from. If not used, all players are used.

None
unit_list List[Unit]

A set of units to filter from. If not used, all units are used.

None

Returns:

Type Description
List[Unit]

A list of units

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def filter_units_by_const(
        self,
        unit_consts: List[int],
        blacklist: bool = False,
        player_list: List[Union[int, PlayerId]] = None,
        unit_list: List[Unit] = None
) -> List[Unit]:
    """
    Filter unit on their unit_const value.

    Args:
        unit_consts: The constants to filter with
        blacklist: Use the given constant list as blacklist instead of whitelist
        player_list: A list of players to filter from. If not used, all players are used.
        unit_list: A set of units to filter from. If not used, all units are used.

    Returns:
        A list of units
    """
    return self.filter_units_by("unit_const", unit_consts, blacklist, player_list, unit_list)

def filter_units_by_reference_id(...)

Filter unit on their unit_const value.

Parameters:

Name Type Description Default
unit_reference_ids List[int]

The reference_ids to filter with

required
blacklist bool

Use the given constant list as blacklist instead of whitelist

False
player_list List[int]

A list of players to filter from. If not used, all players are used.

None
unit_list List[Unit]

A set of units to filter from. If not used, all units are used.

None

Returns:

Type Description
List[Unit]

A list of units

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def filter_units_by_reference_id(
        self,
        unit_reference_ids: List[int],
        blacklist: bool = False,
        player_list: List[Union[int, PlayerId]] = None,
        unit_list: List[Unit] = None
) -> List[Unit]:
    """
    Filter unit on their unit_const value.

    Args:
        unit_reference_ids (List[int]): The reference_ids to filter with
        blacklist (bool): Use the given constant list as blacklist instead of whitelist
        player_list (List[int]): A list of players to filter from. If not used, all players are used.
        unit_list (List[Unit]): A set of units to filter from. If not used, all units are used.

    Returns:
        A list of units
    """
    return self.filter_units_by("reference_id", unit_reference_ids, blacklist, player_list, unit_list)

def find_highest_reference_id(...)

Find the highest ID in the map. Searches through all units for the highest ID.

Returns:

Type Description
int

The highest ID in the map

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
358
359
360
361
362
363
364
365
366
367
368
369
def find_highest_reference_id(self) -> int:
    """
    Find the highest ID in the map. Searches through all units for the highest ID.

    Returns:
        The highest ID in the map
    """
    highest_id = 0  # If no units, default to 0
    for player in PlayerId.all():
        for unit in self.units[player]:
            highest_id = max(highest_id, unit.reference_id)
    return highest_id

def get_all_units(...)
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
179
180
181
182
183
def get_all_units(self) -> List[Unit]:
    units = []
    for player_units in self.units:
        units += player_units
    return units

def get_new_reference_id(...)

Get a new ID each time the function is called. Starting from the current highest ID.

Returns:

Type Description
int

The newly generated ID

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
349
350
351
352
353
354
355
356
def get_new_reference_id(self) -> int:
    """
    Get a new ID each time the function is called. Starting from the current highest ID.

    Returns:
        The newly generated ID
    """
    return next(self.reference_id_generator)

def get_player_units(...)

Returns a list of UnitObjects for the given player.

Raises:

Type Description
ValueError

If player is not between 0 (GAIA) and 8 (EIGHT)

Parameters:

Name Type Description Default
player int | PlayerId - required
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
168
169
170
171
172
173
174
175
176
177
def get_player_units(self, player: int | PlayerId) -> List[Unit]:
    """
    Returns a list of UnitObjects for the given player.

    Raises:
        ValueError: If player is not between 0 (GAIA) and 8 (EIGHT)
    """
    if not 0 <= player <= 8:
        raise ValueError("Player must have a value between 0 and 8")
    return self.units[player]

def get_units_in_area(...)

Returns all units in the square with left corner (x1, y1) and right corner (x2, y2). Both corners inclusive.

Parameters:

Name Type Description Default
x1 float

The X location of the left corner

None
y1 float

The Y location of the left corner

None
x2 float

The X location of the right corner

None
y2 float

The Y location of the right corner

None
tile1 Tile

The x,y location of the 1st corner as Tile Object

None
tile2 Tile

The x,y location of the 2nd corner as Tile Object

None
unit_list List[Unit]

(Optional) A list of units (Defaults to all units in the map, including GAIA (Trees etc.)

None
players List[Union[int, PlayerId]]

(Optional) A list of Players which units need to be selected from the selected area

None
ignore_players List[PlayerId]

(Optional) A list of Players which units need to be ignored from the selected area

None

Raises:

Type Description
ValueError

if not all 4 (x1, y1, x2 and y2) are used simultaneously. Or if both (tile1 and tile2) are not used simultaneously. Or if any of the 4 (x1, y1, x2, y2) is used together with any of (tile1, tile2). Use one or the other. Or if players and ignore_players are used simultaneously.

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
def get_units_in_area(
        self,
        x1: float = None,
        y1: float = None,
        x2: float = None,
        y2: float = None,
        tile1: Tile = None,
        tile2: Tile = None,
        unit_list: List[Unit] = None,
        players: List[Union[int, PlayerId]] = None,
        ignore_players: List[PlayerId] = None
) -> List[Unit]:
    """
    Returns all units in the square with left corner (x1, y1) and right corner (x2, y2). Both corners inclusive.

    Args:
        x1: The X location of the left corner
        y1: The Y location of the left corner
        x2: The X location of the right corner
        y2: The Y location of the right corner
        tile1: The x,y location of the 1st corner as Tile Object
        tile2: The x,y location of the 2nd corner as Tile Object
        unit_list: (Optional) A list of units (Defaults to all units in the map, including GAIA (Trees etc.)
        players: (Optional) A list of Players which units need to be selected from the selected area
        ignore_players: (Optional) A list of Players which units need to be ignored from the selected area

    Raises:
        ValueError: if not all 4 (x1, y1, x2 and y2) are used simultaneously.
            Or if both (tile1 and tile2) are not used simultaneously.
            Or if any of the 4 (x1, y1, x2, y2) is used together with any of (tile1, tile2). Use one or the other.
            Or if players and ignore_players are used simultaneously.
    """
    if (x1 is not None or y1 is not None or x2 is not None or y2 is not None) and any([tile1, tile2]):
        raise ValueError("Cannot use both x1,y1,x2,y2 notation and tile1,tile2 notation at the same time")
    if (x1 is not None or y1 is not None or x2 is not None or y2 is not None) and \
            (x1 is None or y1 is None or x2 is None or y2 is None):
        raise ValueError("Cannot use some but not all from x1,y1,x2,y2.")
    if (not all([tile1, tile2])) and any([tile1, tile2]):
        raise ValueError("Cannot use one from tile1, tile2. Use both.")
    if players is not None and ignore_players is not None:
        raise ValueError("Cannot use both whitelist (players) and blacklist (ignore_players) at the same time")

    if tile1:
        x1 = tile1.x
        y1 = tile1.y
        x2 = tile2.x
        y2 = tile2.y
    else:
        # Inclusive selection
        x2 += 1
        y2 += 1

    if players is not None:
        players = players
    elif ignore_players is not None:
        players = [p for p in PlayerId if p not in ignore_players]
    else:
        players = [p for p in PlayerId]

    if unit_list is None:
        unit_list = self.get_all_units()

    return [unit for unit in unit_list if x1 <= unit.x <= x2 and y1 <= unit.y <= y2 and unit.player in players]

def remove_eye_candy(...)
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
395
396
397
def remove_eye_candy(self) -> None:
    eye_candy_ids = [1351, 1352, 1353, 1354, 1355, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366]
    self.units[0] = [gaia_unit for gaia_unit in self.units[0] if gaia_unit.unit_const not in eye_candy_ids]

def remove_unit(...)

Removes a unit. Please note that unit=... is a lot faster than reference_id=... due to reference_id having to search through all units on the map. And unit has an ownership (player) attribute which is used for knowing which list to remove the unit from.

Parameters:

Name Type Description Default
reference_id int

The id of the unit. Note that this is NOT a unit constant (So NOT: UnitInfo.ARCHER)

None
unit Unit

The Unit object to be removed.

None
Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
def remove_unit(self, reference_id: int = None, unit: Unit = None) -> None:
    """
    Removes a unit. Please note that `unit=...` is a lot faster than `reference_id=...` due to reference_id having
    to search through all units on the map. And unit has an ownership (player) attribute which is used for knowing
    which list to remove the unit from.

    Args:
        reference_id: The id of the unit. Note that this is NOT a unit constant (So NOT: UnitInfo.ARCHER)
        unit: The Unit object to be removed.
    """
    if reference_id is not None and unit is not None:
        raise ValueError("Cannot use both unit_ref_id and unit arguments. Use one or the other.")
    if reference_id is None and unit is None:
        raise ValueError("Both unit_ref_id and unit arguments were unused. Use one.")

    if reference_id is not None:
        for player in range(0, 9):
            for i, unit in enumerate(self.units[player]):
                if unit.reference_id == reference_id:
                    del self.units[player][i]
                    return
    elif unit is not None:
        self.units[unit.player].remove(unit)

def update_unit_player_values(...)

Function to update all player values in all units. Useful when units are moved manually (in mass).

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
53
54
55
56
57
def update_unit_player_values(self):
    """Function to update all player values in all units. Useful when units are moved manually (in mass)."""
    for player in PlayerId.all():
        for unit in self.units[player]:
            unit._player = player

Functions


def create_id_generator(...)

Create generator for increasing value

Parameters:

Name Type Description Default
start_id int

The id to start returning

required

Returns:

Type Description
Generator[int]

A generator which will return a +1 ID value for each time called with next.

Source code in AoE2ScenarioParser/objects/managers/unit_manager.py
413
414
415
416
417
418
419
420
421
422
423
424
425
def create_id_generator(start_id: int) -> Generator[int]:
    """
    Create generator for increasing value

    Args:
        start_id: The id to start returning

    Returns:
        A generator which will return a +1 ID value for each time called with next.
    """
    while True:
        yield start_id
        start_id += 1