class Variable(AoE2Object): """Object for handling a variable.""" _link_list = [ RetrieverObjectLink("variable_id", "Triggers", "variable_data[__index__].variable_id"), RetrieverObjectLink("name", "Triggers", "variable_data[__index__].variable_name"), ] def __init__(self, variable_id: int, name: str): self.variable_id = variable_id self.name = name super().__init__()
class MapManagerDE(MapManager): _link_list = [ RetrieverObjectLink("map_color_mood", "Map", "map_color_mood"), RetrieverObjectLink("collide_and_correct", "Map", "collide_and_correct"), RetrieverObjectLink("villager_force_drop", "Map", "villager_force_drop"), RetrieverObjectLink("map_width", "Map", "map_width"), RetrieverObjectLink("map_height", "Map", "map_height"), RetrieverObjectLink("terrain", "Map", "terrain_data", process_as_object=TerrainTile), RetrieverObjectLink("script_name", "Map", "script_name", Support(since=1.40)), ] def __init__(self, map_color_mood: str, collide_and_correct: bool, villager_force_drop: bool, map_width: int, map_height: int, terrain: List[TerrainTile], script_name: str ): self.map_color_mood = map_color_mood self.collide_and_correct = collide_and_correct self.villager_force_drop = villager_force_drop self.script_name = script_name super().__init__(map_width, map_height, terrain)
class UnitManagerDE(UnitManager): _link_list = [ RetrieverObjectLink("units", "Units", "players_units[].units", process_as_object=Unit) ] def __init__(self, units: List[List[Unit]]): super().__init__(units)
class Trigger(AoE2Object): """Object for handling a trigger.""" _link_list = [ RetrieverObjectLink("name", "Triggers", "trigger_data[__index__].trigger_name"), RetrieverObjectLink("description", "Triggers", "trigger_data[__index__].trigger_description"), RetrieverObjectLink( "description_stid", "Triggers", "trigger_data[__index__].description_string_table_id"), RetrieverObjectLink("display_as_objective", "Triggers", "trigger_data[__index__].display_as_objective"), RetrieverObjectLink("short_description", "Triggers", "trigger_data[__index__].short_description"), RetrieverObjectLink( "short_description_stid", "Triggers", "trigger_data[__index__].short_description_string_table_id"), RetrieverObjectLink("display_on_screen", "Triggers", "trigger_data[__index__].display_on_screen"), RetrieverObjectLink( "description_order", "Triggers", "trigger_data[__index__].objective_description_order"), RetrieverObjectLink("enabled", "Triggers", "trigger_data[__index__].enabled"), RetrieverObjectLink("looping", "Triggers", "trigger_data[__index__].looping"), RetrieverObjectLink("header", "Triggers", "trigger_data[__index__].make_header"), RetrieverObjectLink("mute_objectives", "Triggers", "trigger_data[__index__].mute_objectives"), RetrieverObjectLink("conditions", "Triggers", "trigger_data[__index__].condition_data", process_as_object=Condition), RetrieverObjectLink( "condition_order", "Triggers", "trigger_data[__index__].condition_display_order_array"), RetrieverObjectLink("effects", "Triggers", "trigger_data[__index__].effect_data", process_as_object=Effect), RetrieverObjectLink( "effect_order", "Triggers", "trigger_data[__index__].effect_display_order_array"), RetrieverObjectLink("trigger_id", retrieve_instance_number=True), ] def __init__( self, name: str, description: str = "", description_stid: int = -1, display_as_objective: int = 0, short_description: str = "", short_description_stid: int = -1, display_on_screen: int = 0, description_order: int = 0, enabled: int = 1, looping: int = 0, header: int = 0, mute_objectives: int = 0, conditions: List[Condition] = None, condition_order: List[int] = None, effects: List[Effect] = None, effect_order: List[int] = None, trigger_id: int = -1, ): if conditions is None: conditions = [] if condition_order is None: condition_order = [] if effects is None: effects = [] if effect_order is None: effect_order = [] self.name: str = name self.description: str = description self.description_stid: int = description_stid self.display_as_objective: int = display_as_objective self.short_description: str = short_description self.short_description_stid: int = short_description_stid self.display_on_screen: int = display_on_screen self.description_order: int = description_order self.enabled: int = enabled self.looping: int = looping self.header: int = header self.mute_objectives: int = mute_objectives self._condition_hash = hash_list(conditions) self.conditions: List[Condition] = conditions self.condition_order: List[int] = condition_order self._effect_hash = hash_list(effects) self.effects: List[Effect] = effects self.effect_order: List[int] = effect_order self.trigger_id: int = trigger_id self.new_effect = NewEffectSupport(self) self.new_condition = NewConditionSupport(self) super().__init__() def __deepcopy__(self, memo): cls = self.__class__ result = cls.__new__(cls) memo[id(self)] = result for k, v in self.__dict__.items(): if k in ['new_effect', 'new_condition']: continue setattr(result, k, self._deepcopy_entry(k, v)) return result @property def condition_order(self): if list_changed(self.conditions, self._condition_hash): update_order_array(self._condition_order, len(self.conditions)) self._condition_hash = hash_list(self.conditions) return self._condition_order @condition_order.setter def condition_order(self, val): self._condition_order = val @property def effect_order(self): if list_changed(self.effects, self._effect_hash): update_order_array(self._effect_order, len(self.effects)) self._effect_hash = hash_list(self.effects) return self._effect_order @effect_order.setter def effect_order(self, val): self._effect_order = val @property def conditions(self) -> List[Condition]: return self._conditions @conditions.setter def conditions(self, val: List[Condition]) -> None: self._conditions = val self.condition_order = list(range(0, len(val))) @property def effects(self) -> List[Effect]: return self._effects @effects.setter def effects(self, val: List[Effect]) -> None: self._effects = val self.effect_order = list(range(0, len(val))) def _add_effect(self, effect_type: EffectId, ai_script_goal=None, armour_attack_quantity=None, armour_attack_class=None, quantity=None, tribute_list=None, diplomacy=None, object_list_unit_id=None, source_player=None, target_player=None, technology=None, string_id=None, display_time=None, trigger_id=None, location_x=None, location_y=None, location_object_reference=None, area_x1=None, area_y1=None, area_x2=None, area_y2=None, object_group=None, object_type=None, instruction_panel_position=None, attack_stance=None, time_unit=None, enabled=None, food=None, wood=None, stone=None, gold=None, item_id=None, flash_object=None, force_research_technology=None, visibility_state=None, scroll=None, operation=None, object_list_unit_id_2=None, button_location=None, ai_signal_value=None, object_attributes=None, variable=None, timer=None, facet=None, play_sound=None, message=None, player_color=None, sound_name=None, selected_object_ids=None) -> Effect: """Used to add new effect to trigger. Please use trigger.new_effect.<effect_name> instead""" def get_default_effect_attributes(eff_type): """Gets the default effect attributes based on a certain effect type, with exception handling""" try: return effect_dataset.default_attributes[eff_type] except KeyError: effect = EffectId(eff_type) raise UnsupportedAttributeError( f"The effect {effect.name} is not supported in scenario version {self._scenario_version}" ) from None effect_defaults = get_default_effect_attributes(effect_type) effect_attr = {} for key, value in effect_defaults.items(): effect_attr[key] = (locals()[key] if locals()[key] is not None else value) new_effect = Effect(**effect_attr) self.effects.append(new_effect) return new_effect def _add_condition(self, condition_type: ConditionId, quantity=None, attribute=None, unit_object=None, next_object=None, object_list=None, source_player=None, technology=None, timer=None, area_x1=None, area_y1=None, area_x2=None, area_y2=None, object_group=None, object_type=None, ai_signal=None, inverted=None, variable=None, comparison=None, target_player=None, unit_ai_action=None, xs_function=None) -> Condition: """Used to add new condition to trigger. Please use trigger.new_condition.<condition_name> instead""" def get_default_condition_attributes(cond_type): """Gets the default condition attributes based on a certain condition type, with exception handling""" try: return condition_dataset.default_attributes[cond_type] except KeyError: condition = ConditionId(cond_type) raise UnsupportedAttributeError( f"The condition {condition.name} is not supported in scenario version {self._scenario_version}" ) from None condition_defaults = get_default_condition_attributes(condition_type) condition_attr = {} for key, value in condition_defaults.items(): condition_attr[key] = (locals()[key] if locals()[key] is not None else value) new_condition = Condition(**condition_attr) self.conditions.append(new_condition) return new_condition def get_effect(self, effect_index: int = None, display_index: int = None) -> Effect: if not exclusive_if(effect_index is not None, display_index is not None): raise ValueError( f"Please identify an effect using either effect_index or display_index." ) if effect_index is None: effect_index = self.effect_order[display_index] return self.effects[effect_index] def get_condition(self, condition_index: int = None, display_index: int = None) -> Condition: if not exclusive_if(condition_index is not None, display_index is not None): raise ValueError( f"Please identify a condition using either condition_index or display_index." ) if condition_index is None: condition_index = self.condition_order[display_index] return self.conditions[condition_index] def remove_effect(self, effect_index: int = None, display_index: int = None, effect: Effect = None) -> None: if not exclusive_if(effect_index is not None, display_index is not None, effect is not None): raise ValueError( f"Please identify an effect using either effect_index, display_index or effect." ) if effect is not None: effect_index = self.effects.index(effect) if effect_index is None: effect_index = self.effect_order[display_index] else: display_index = self.effect_order.index(effect_index) del self.effects[effect_index] del self.effect_order[display_index] self.effect_order = [ x - 1 if x > effect_index else x for x in self.effect_order ] def remove_condition(self, condition_index: int = None, display_index: int = None, condition: Condition = None) \ -> None: if not exclusive_if(condition_index is not None, display_index is not None, condition is not None): raise ValueError( f"Please identify a condition using either condition_index, display_index or condition." ) if condition is not None: condition_index = self.conditions.index(condition) if condition_index is None: condition_index = self.condition_order[display_index] else: display_index = self.condition_order.index(condition_index) del self.conditions[condition_index] del self.condition_order[display_index] self.condition_order = [ x - 1 if x > condition_index else x for x in self.condition_order ] def get_content_as_string(self) -> str: return_string = "" data_tba = {'enabled': self.enabled != 0, 'looping': self.looping != 0} if self.description != "": data_tba['description'] = f"'{self.description}'" if self.description_stid != -1: data_tba['description_stid'] = self.description_stid if self.short_description != "": data_tba['short_description'] = f"'{self.short_description}'" if self.short_description_stid != -1: data_tba['short_description_stid'] = self.short_description_stid if self.display_as_objective != 0: data_tba['display_as_objective'] = (self.display_as_objective != 0) if self.display_on_screen != 0: data_tba['display_on_screen'] = (self.display_on_screen != 0) if self.description_order != 0: data_tba['description_order'] = self.description_order if self.header != 0: data_tba['header'] = (self.header != 0) if self.mute_objectives != 0: data_tba['mute_objectives'] = (self.mute_objectives != 0) for key, value in data_tba.items(): return_string += f"\t\t{key}: {value}\n" if len(self.condition_order) > 0: return_string += "\t\tconditions:\n" for c_display_order, condition_id in enumerate( self.condition_order): condition = self.conditions[condition_id] return_string += f"\t\t\t{condition_dataset.condition_names[condition.condition_type]} " \ f"[Index: {condition_id}, Display: {c_display_order}]:\n" return_string += condition.get_content_as_string() if len(self.effect_order) > 0: return_string += "\t\teffects:\n" for e_display_order, effect_id in enumerate(self.effect_order): effect = self.effects[effect_id] return_string += f"\t\t\t{effect_dataset.effect_names[effect.effect_type]}" \ f" [Index: {effect_id}, Display: {e_display_order}]:\n" return_string += effect.get_content_as_string() return return_string
class Unit(AoE2Object): _link_list = [ RetrieverObjectLink("player", retrieve_history_number=0), RetrieverObjectLink("x", "Units", "players_units[__index__].units[__index__].x"), RetrieverObjectLink("y", "Units", "players_units[__index__].units[__index__].y"), RetrieverObjectLink("z", "Units", "players_units[__index__].units[__index__].z"), RetrieverObjectLink( "reference_id", "Units", "players_units[__index__].units[__index__].reference_id"), RetrieverObjectLink( "unit_const", "Units", "players_units[__index__].units[__index__].unit_const"), RetrieverObjectLink( "status", "Units", "players_units[__index__].units[__index__].status"), RetrieverObjectLink( "rotation", "Units", "players_units[__index__].units[__index__].rotation"), RetrieverObjectLink( "initial_animation_frame", "Units", "players_units[__index__].units[__index__].initial_animation_frame" ), RetrieverObjectLink( "garrisoned_in_id", "Units", "players_units[__index__].units[__index__].garrisoned_in_id"), ] def __init__(self, player: PlayerId, x: float, y: float, z: float, reference_id: int, unit_const: int, status: int, rotation: float, initial_animation_frame: int, garrisoned_in_id: int): raise_if_not_int_subclass([unit_const]) self._player: PlayerId = PlayerId(player) """ PLEASE NOTE: This is an internal (read-only) value for ease of access. It accurately represent the actual player controlling the unit but is not directly connected to it. Changing this value will have no impact to your scenario. To change which player controls this unit, use: unit_manager.change_ownership(Unit, to_player) """ self.x: float = x self.y: float = y self.z: float = z self.reference_id: int = reference_id self.unit_const: int = unit_const self.status: int = status self.rotation: float = rotation self.initial_animation_frame: int = initial_animation_frame self.garrisoned_in_id: int = garrisoned_in_id super().__init__() @property def player(self) -> PlayerId: """ PLEASE NOTE: This is an internal (read-only) value for ease of access. It DOES accurately represent the actual player controlling the unit BUT IT IS NOT directly connected to it. Changing this value will have no impact to your scenario. To change which player controls this unit, use: unit_manager.change_ownership(Unit, to_player) """ return self._player @property def tile(self) -> Tile: return Tile(math.floor(self.x), math.floor(self.y)) # Floor x and y as location (0.9, 0.9) is still Tile[x=0, y=0] @tile.setter def tile(self, tile: Tile) -> None: self.x = tile.x self.y = tile.y @property def name(self) -> str: unit_enum = helper.get_enum_from_unit_const(self.unit_const) if unit_enum: return pretty_format_name(unit_enum.name) else: return f"Unknown{self.unit_const}" # e.g."Unknown411"
class TriggerManager(AoE2Object): """Manager of the everything trigger related.""" _link_list = [ RetrieverObjectLink("triggers", "Triggers", "trigger_data", process_as_object=Trigger), RetrieverObjectLink("trigger_display_order", "Triggers", "trigger_display_order_array"), ] def __init__(self, triggers: List[Trigger], trigger_display_order: List[int], ): self._trigger_hash = hash_list(triggers) self.triggers: List[Trigger] = triggers self.trigger_display_order: List[int] = trigger_display_order super().__init__() @property def triggers(self): return self._triggers @triggers.setter def triggers(self, value): self._trigger_hash = hash_list(value) self._triggers = value self.trigger_display_order = list(range(len(value))) @property def trigger_display_order(self): if list_changed(self.triggers, self._trigger_hash): update_order_array(self._trigger_display_order, len(self.triggers)) self._trigger_hash = hash_list(self.triggers) return self._trigger_display_order @trigger_display_order.setter def trigger_display_order(self, val): self._trigger_display_order = val def copy_trigger_per_player(self, from_player, trigger_select, change_from_player_only=False, include_player_source=True, include_player_target=False, trigger_ce_lock=None, include_gaia: bool = False, create_copy_for_players: List[IntEnum] = None) -> Dict[PlayerId, Trigger]: """ Copies a trigger for all or a selection of players. Every copy will change desired player attributes with it. Args: from_player (IntEnum): The central player this trigger is created for. This is the player that will not get a copy. trigger_select (TriggerSelect): An object used to identify which trigger to select. change_from_player_only (bool): If set to True, only change player attributes in effects and conditions that are equal to the player defined using the `from_player` parameter. include_player_source (bool): If set to True, allow player source attributes to be changed while copying. Player source attributes are attributes where a player is defined to perform an action such as create an object. If set to False these attributes will remain unchanged. include_player_target (bool): If set to True, allow player target attributes to be changed while copying. Player target attributes are attributes where a player is defined as the target such as change ownership or sending resources. If set to False these attributes will remain unchanged. trigger_ce_lock (TriggerCELock): The TriggerCELock object. Used to lock certain (types) of conditions or effects from being changed while copying. include_gaia (bool): If True, GAIA is included in the copied list. (Also when `create_copy_for_players` is defined) create_copy_for_players (List[IntEnum]): A list of Players to create a copy for. The `from_player` will be excluded from this list. Returns: A dict with all the new created triggers. The key is the player for which the trigger is created using the IntEnum associated with it. Example: {PlayerId.TWO: Trigger, PlayerId.FIVE: Trigger} Raises: ValueError: if more than one trigger selection is used. Any of (trigger_index, display_index or trigger) Or if Both `include_player_source` and `include_player_target` are `False` :Authors: KSneijders """ trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) if not include_player_source and not include_player_target: raise ValueError("Cannot exclude player source and target.") if create_copy_for_players is None: create_copy_for_players = [ PlayerId.ONE, PlayerId.TWO, PlayerId.THREE, PlayerId.FOUR, PlayerId.FIVE, PlayerId.SIX, PlayerId.SEVEN, PlayerId.EIGHT ] if include_gaia and PlayerId.GAIA not in create_copy_for_players: create_copy_for_players.append(PlayerId.GAIA) alter_conditions, alter_effects = TriggerManager._find_alterable_ce(trigger, trigger_ce_lock) return_dict: Dict[PlayerId, Trigger] = {} for player in create_copy_for_players: if not player == from_player: new_trigger = self.copy_trigger(TS.trigger(trigger)) new_trigger.name += f" (p{player})" return_dict[player] = new_trigger for cond_x in alter_conditions: cond = new_trigger.conditions[cond_x] # Player not set if cond.source_player == -1: continue # Player not equal to 'from_player' if change_from_player_only: if not cond.source_player == from_player: continue # Change source player if include_player_source: cond.source_player = PlayerId(player) # Change target player if include_player_target: cond.target_player = PlayerId(player) for effect_x in alter_effects: effect = new_trigger.effects[effect_x] # Player not set if effect.source_player == -1: continue # Player not equal to 'from_player' if change_from_player_only: if not effect.source_player == from_player: continue # Change source player if include_player_source: effect.source_player = PlayerId(player) # Change target player if include_player_target: effect.target_player = PlayerId(player) return return_dict def copy_trigger(self, trigger_select) -> Trigger: """ Creates an exact copy (deepcopy) of this trigger. Args: trigger_select (TriggerSelect): An object used to identify which trigger to select. Returns: The newly copied trigger """ trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) deepcopy_trigger = copy.deepcopy(trigger) deepcopy_trigger.name += " (copy)" deepcopy_trigger.trigger_id = len(self.triggers) self.triggers.append(deepcopy_trigger) return deepcopy_trigger def copy_trigger_tree_per_player(self, from_player, trigger_select, change_from_player_only=False, include_player_source=True, include_player_target=False, trigger_ce_lock=None, include_gaia: bool = False, create_copy_for_players: List[IntEnum] = None, group_triggers_by=None): """ Copies an entire trigger tree for all or a selection of players. Every copy will change desired player attributes with it. Trigger trees are triggers linked together using EffectId.(DE)ACTIVATE_TRIGGER. Args: from_player (IntEnum): The central player this trigger is created for. This is the player that will not get a copy. trigger_select (TriggerSelect): An object used to identify which trigger to select. change_from_player_only (bool): If set to True, only change player attributes in effects and conditions that are equal to the player defined using the `from_player` parameter. include_player_source (bool): If set to True, allow player source attributes to be changed while copying. Player source attributes are attributes where a player is defined to perform an action such as create an object. If set to False these attributes will remain unchanged. include_player_target (bool): If set to True, allow player target attributes to be changed while copying. Player target attributes are attributes where a player is defined as the target such as change ownership or sending resources. If set to False these attributes will remain unchanged. trigger_ce_lock (TriggerCELock): The TriggerCELock object. Used to lock certain (types) of conditions or effects from being changed while copying. include_gaia (bool): If True, GAIA is included in the copied list. (Also when `create_copy_for_players` is defined) create_copy_for_players (List[IntEnum]): A list of Players to create a copy for. The `from_player` will be excluded from this list. group_triggers_by (GroupBy): How to group the newly added triggers. Returns: The newly created triggers in a dict using the Player as key and as value with a list of triggers """ if group_triggers_by is None: group_triggers_by = GroupBy.NONE trigger_index, display_index, source_trigger = self._validate_and_retrieve_trigger_info(trigger_select) known_node_indexes = [trigger_index] self._find_trigger_tree_nodes_recursively(source_trigger, known_node_indexes) new_triggers = {} id_swap = {} for index in known_node_indexes: triggers = self.copy_trigger_per_player( from_player, TS.index(index), change_from_player_only, include_player_source, include_player_target, trigger_ce_lock, include_gaia, create_copy_for_players, ) for player, trigger in triggers.items(): id_swap.setdefault(index, {})[player] = trigger.trigger_id new_triggers.setdefault(player, []).append(trigger) for player, triggers in new_triggers.items(): for trigger in triggers: activation_effects = [ effect for effect in trigger.effects if effect.effect_type in [EffectId.ACTIVATE_TRIGGER, EffectId.DEACTIVATE_TRIGGER] ] for effect in activation_effects: effect.trigger_id = id_swap[effect.trigger_id][player] # Group by logic if group_triggers_by == GroupBy.TRIGGER: for index, source_trigger_id in enumerate(known_node_indexes): for player, trigger in [(player, triggers[index]) for player, triggers in new_triggers.items()]: # When going negative (going 'below' the source already happens at insert @ 0 display_index_offset = player - from_player if from_player <= player else 0 source_trigger_display_index = self.trigger_display_order.index(source_trigger_id) self.trigger_display_order.remove(trigger.trigger_id) new_display_index = max(0, source_trigger_display_index + display_index_offset) self.trigger_display_order.insert( new_display_index, trigger.trigger_id ) elif group_triggers_by == GroupBy.PLAYER: source_trigger_display_index = display_index source_trigger_offset = 0 # Group known tree nodes for tree_trigger_id in known_node_indexes: self.trigger_display_order.remove(tree_trigger_id) self.trigger_display_order.insert( source_trigger_display_index + source_trigger_offset, tree_trigger_id ) source_trigger_offset += 1 # Group copied triggers for player, triggers in new_triggers.items(): source_trigger_display_index = self.trigger_display_order.index(source_trigger.trigger_id) source_trigger_offset = 0 display_index_offset = (player - from_player if from_player <= player else 0) * len(known_node_indexes) for trigger in triggers: final_offset = max(source_trigger_display_index + display_index_offset + source_trigger_offset, 0) self.trigger_display_order.remove(trigger.trigger_id) self.trigger_display_order.insert( final_offset, trigger.trigger_id ) source_trigger_offset += 1 return new_triggers def copy_trigger_tree(self, trigger_select: TriggerSelect) -> List[Trigger]: trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) known_node_indexes = [trigger_index] self._find_trigger_tree_nodes_recursively(trigger, known_node_indexes) new_triggers = [] id_swap = {} for index in known_node_indexes: trigger = self.copy_trigger(TS.index(index)) new_triggers.append(trigger) id_swap[index] = trigger.trigger_id for trigger in new_triggers: activation_effects = [ effect for effect in trigger.effects if effect.effect_type in [EffectId.ACTIVATE_TRIGGER, EffectId.DEACTIVATE_TRIGGER] ] for effect in activation_effects: effect.trigger_id = id_swap[effect.trigger_id] return new_triggers def replace_player(self, trigger_select, to_player, only_change_from=None, include_player_source=True, include_player_target=False, trigger_ce_lock=None) -> Trigger: """ Replaces player attributes. Specifically useful if multiple players are used in the same trigger. Args: trigger_select (TriggerSelect): An object used to identify which trigger to select. to_player (PlayerId): The player the attributes are changed to. only_change_from (PlayerId): Can only change player attributes if the player is equal to the given value include_player_source (bool): If set to True, allow player source attributes to be changed while replacing. Player source attributes are attributes where a player is defined to perform an action such as create an object. If set to False these attributes will remain unchanged. include_player_target (bool): If set to True, allow player target attributes to be changed while replacing. Player target attributes are attributes where a player is defined as the target such as change ownership or sending resources. If set to False these attributes will remain unchanged. trigger_ce_lock (TriggerCELock): The TriggerCELock object. Used to lock certain (types) of conditions or effects from being changed. Returns: The given trigger with the proper player attributes changed """ trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) alter_conditions, alter_effects = TriggerManager._find_alterable_ce(trigger, trigger_ce_lock) for cond_x in alter_conditions: cond = trigger.conditions[cond_x] if not cond.source_player == -1 and include_player_source: if only_change_from is not None and only_change_from != cond.source_player: continue cond.source_player = PlayerId(to_player) if not cond.target_player == -1 and include_player_target: if only_change_from is not None and only_change_from != cond.target_player: continue cond.target_player = PlayerId(to_player) for effect_x in alter_effects: effect = trigger.effects[effect_x] if not effect.source_player == -1 and include_player_source: if only_change_from is not None and only_change_from != effect.source_player: continue effect.source_player = PlayerId(to_player) if not effect.target_player == -1 and include_player_target: if only_change_from is not None and only_change_from != effect.target_player: continue effect.target_player = PlayerId(to_player) return trigger def add_trigger(self, name, description=None, description_stid=None, display_as_objective=None, short_description=None, short_description_stid=None, display_on_screen=None, description_order=None, enabled=None, looping=None, header=None, mute_objectives=None, conditions=None, effects=None) -> Trigger: """ Adds a new trigger to the scenario. Args: name (str): The name for the trigger description (str): The trigger description description_stid (int): The trigger description string table ID display_as_objective (bool): Display the trigger as objective short_description (str): The short trigger description short_description_stid (int): The short trigger description string table ID display_on_screen (bool): Display the trigger objective on screen description_order (int): ? enabled (bool): If the trigger is enabled from the start. looping (bool): If the trigger loops. header (bool): Turn objective into header mute_objectives (bool): Mute objectives conditions (List): A list of condition managers effects (List): A list of effect managers Returns: The newly created trigger """ keys = [ 'description', 'description_stid', 'display_as_objective', 'short_description', 'short_description_stid', 'display_on_screen', 'description_order', 'enabled', 'looping', 'header', 'mute_objectives', 'conditions', 'effects' ] trigger_attr = {} for key in keys: if locals()[key] is not None: trigger_attr[key] = locals()[key] new_trigger = Trigger(name=name, trigger_id=len(self.triggers), **trigger_attr) self.triggers.append(new_trigger) return new_trigger def get_trigger(self, trigger_select: TriggerSelect) -> Trigger: trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) return trigger def remove_trigger(self, trigger_select: TriggerSelect) -> None: trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) for x in self.trigger_display_order: if x > trigger_index: self.get_trigger(TS.index(x)).trigger_id -= 1 del self.triggers[trigger_index] del self.trigger_display_order[display_index] self.trigger_display_order = [x - 1 if x > trigger_index else x for x in self.trigger_display_order] def _find_trigger_tree_nodes_recursively(self, trigger, known_node_indexes: List[int]) -> None: found_node_indexes = TriggerManager._find_trigger_tree_nodes(trigger) unknown_node_indexes = [i for i in found_node_indexes if i not in known_node_indexes] if len(unknown_node_indexes) == 0: return known_node_indexes += unknown_node_indexes for index in unknown_node_indexes: self._find_trigger_tree_nodes_recursively(self.triggers[index], known_node_indexes) def _validate_and_retrieve_trigger_info(self, trigger_select) -> (int, int, Trigger): trigger = trigger_select.trigger trigger_index = trigger_select.trigger_index display_index = trigger_select.display_index if trigger is not None: trigger_index = trigger.trigger_id display_index = self.trigger_display_order.index(trigger_index) elif trigger_index is not None: trigger = self.triggers[trigger_index] display_index = self.trigger_display_order.index(trigger_index) elif display_index is not None: trigger_index = self.trigger_display_order[display_index] trigger = self.triggers[trigger_index] return trigger_index, display_index, trigger def get_summary_as_string(self) -> str: return_string = "\nTrigger Summary:\n" triggers = self.triggers display_order = self.trigger_display_order if len(display_order) == 0: return_string += "\t<< No Triggers >>" longest_trigger_name = -1 longest_index_notation = -1 for display, trigger_index in enumerate(display_order): trigger_name = triggers[trigger_index].name longest_trigger_name = max(longest_trigger_name, len(trigger_name)) longest_index_notation = max( longest_index_notation, helper.get_int_len(display) + helper.get_int_len(trigger_index) ) longest_trigger_name += 3 for display, trigger_index in enumerate(display_order): trigger = triggers[trigger_index] trigger_name = trigger.name name_buffer = longest_trigger_name - len(trigger_name) index_buffer = longest_index_notation - (helper.get_int_len(display) + helper.get_int_len(trigger_index)) return_string += "\t" + trigger_name + (" " * name_buffer) return_string += f" [Index: {trigger_index}, Display: {display}] {' ' * index_buffer}" return_string += "\t(conditions: " + str(len(trigger.conditions)) + ", " return_string += " effects: " + str(len(trigger.effects)) + ")\n" return return_string def get_content_as_string(self) -> str: return_string = "\nTriggers:\n" if len(self.triggers) == 0: return_string += "\t<<No triggers>>\n" for trigger_index in self.trigger_display_order: return_string += self.get_trigger_as_string(TS.index(trigger_index)) + "\n" return_string += "Variables:\n" return return_string def get_trigger_as_string(self, trigger_select: TriggerSelect) -> str: trigger_index, display_index, trigger = self._validate_and_retrieve_trigger_info(trigger_select) return_string = "\t'" + trigger.name + "'" return_string += " [Index: " + str(trigger_index) + ", Display: " + str(display_index) + "]" + ":\n" return_string += trigger.get_content_as_string() return return_string @staticmethod def _find_alterable_ce(trigger, trigger_ce_lock) -> (List[int], List[int]): lock_conditions = trigger_ce_lock.lock_conditions if trigger_ce_lock is not None else False lock_effects = trigger_ce_lock.lock_effects if trigger_ce_lock is not None else False lock_condition_type = trigger_ce_lock.lock_condition_type if trigger_ce_lock is not None else [] lock_effect_type = trigger_ce_lock.lock_effect_type if trigger_ce_lock is not None else [] lock_condition_ids = trigger_ce_lock.lock_condition_ids if trigger_ce_lock is not None else [] lock_effect_ids = trigger_ce_lock.lock_effect_ids if trigger_ce_lock is not None else [] alter_conditions: List[int] = [] alter_effects: List[int] = [] if not lock_conditions: for i, cond in enumerate(trigger.conditions): if i not in lock_condition_ids and cond.condition_type not in lock_condition_type: alter_conditions.append(i) if not lock_effects: for i, effect in enumerate(trigger.effects): if i not in lock_effect_ids and effect.effect_type not in lock_effect_type: alter_effects.append(i) return alter_conditions, alter_effects @staticmethod def _find_trigger_tree_nodes(trigger: Trigger) -> List[int]: return [ effect.trigger_id for effect in trigger.effects if effect.effect_type in [EffectId.ACTIVATE_TRIGGER, EffectId.DEACTIVATE_TRIGGER] ]
class Effect(AoE2Object): """Object for handling an effect.""" _link_list = [ RetrieverObjectLink( "effect_type", "Triggers", "trigger_data[__index__].effect_data[__index__].effect_type"), RetrieverObjectLink( "ai_script_goal", "Triggers", "trigger_data[__index__].effect_data[__index__].ai_script_goal"), RetrieverObjectLink( "armour_attack_quantity", "Triggers", "trigger_data[__index__].effect_data[__index__].armour_attack_quantity" ), RetrieverObjectLink( "armour_attack_class", "Triggers", "trigger_data[__index__].effect_data[__index__].armour_attack_class" ), RetrieverObjectLink( "quantity", "Triggers", "trigger_data[__index__].effect_data[__index__].quantity"), RetrieverObjectLink( "tribute_list", "Triggers", "trigger_data[__index__].effect_data[__index__].tribute_list"), RetrieverObjectLink( "diplomacy", "Triggers", "trigger_data[__index__].effect_data[__index__].diplomacy"), RetrieverObjectLink( "object_list_unit_id", "Triggers", "trigger_data[__index__].effect_data[__index__].object_list_unit_id" ), RetrieverObjectLink( "source_player", "Triggers", "trigger_data[__index__].effect_data[__index__].source_player"), RetrieverObjectLink( "target_player", "Triggers", "trigger_data[__index__].effect_data[__index__].target_player"), RetrieverObjectLink( "technology", "Triggers", "trigger_data[__index__].effect_data[__index__].technology"), RetrieverObjectLink( "string_id", "Triggers", "trigger_data[__index__].effect_data[__index__].string_id"), RetrieverObjectLink( "display_time", "Triggers", "trigger_data[__index__].effect_data[__index__].display_time"), RetrieverObjectLink( "trigger_id", "Triggers", "trigger_data[__index__].effect_data[__index__].trigger_id"), RetrieverObjectLink( "location_x", "Triggers", "trigger_data[__index__].effect_data[__index__].location_x"), RetrieverObjectLink( "location_y", "Triggers", "trigger_data[__index__].effect_data[__index__].location_y"), RetrieverObjectLink( "location_object_reference", "Triggers", "trigger_data[__index__].effect_data[__index__].location_object_reference" ), RetrieverObjectLink( "area_1_x", "Triggers", "trigger_data[__index__].effect_data[__index__].area_1_x"), RetrieverObjectLink( "area_1_y", "Triggers", "trigger_data[__index__].effect_data[__index__].area_1_y"), RetrieverObjectLink( "area_2_x", "Triggers", "trigger_data[__index__].effect_data[__index__].area_2_x"), RetrieverObjectLink( "area_2_y", "Triggers", "trigger_data[__index__].effect_data[__index__].area_2_y"), RetrieverObjectLink( "object_group", "Triggers", "trigger_data[__index__].effect_data[__index__].object_group"), RetrieverObjectLink( "object_type", "Triggers", "trigger_data[__index__].effect_data[__index__].object_type"), RetrieverObjectLink( "instruction_panel_position", "Triggers", "trigger_data[__index__].effect_data[__index__].instruction_panel_position" ), RetrieverObjectLink( "attack_stance", "Triggers", "trigger_data[__index__].effect_data[__index__].attack_stance"), RetrieverObjectLink( "time_unit", "Triggers", "trigger_data[__index__].effect_data[__index__].time_unit"), RetrieverObjectLink( "enabled", "Triggers", "trigger_data[__index__].effect_data[__index__].enabled"), RetrieverObjectLink( "food", "Triggers", "trigger_data[__index__].effect_data[__index__].food"), RetrieverObjectLink( "wood", "Triggers", "trigger_data[__index__].effect_data[__index__].wood"), RetrieverObjectLink( "stone", "Triggers", "trigger_data[__index__].effect_data[__index__].stone"), RetrieverObjectLink( "gold", "Triggers", "trigger_data[__index__].effect_data[__index__].gold"), RetrieverObjectLink( "item_id", "Triggers", "trigger_data[__index__].effect_data[__index__].item_id"), RetrieverObjectLink( "flash_object", "Triggers", "trigger_data[__index__].effect_data[__index__].flash_object"), RetrieverObjectLink( "force_research_technology", "Triggers", "trigger_data[__index__].effect_data[__index__].force_research_technology" ), RetrieverObjectLink( "visibility_state", "Triggers", "trigger_data[__index__].effect_data[__index__].visibility_state"), RetrieverObjectLink( "scroll", "Triggers", "trigger_data[__index__].effect_data[__index__].scroll"), RetrieverObjectLink( "operation", "Triggers", "trigger_data[__index__].effect_data[__index__].operation"), RetrieverObjectLink( "object_list_unit_id_2", "Triggers", "trigger_data[__index__].effect_data[__index__].object_list_unit_id_2" ), RetrieverObjectLink( "button_location", "Triggers", "trigger_data[__index__].effect_data[__index__].button_location"), RetrieverObjectLink( "ai_signal_value", "Triggers", "trigger_data[__index__].effect_data[__index__].ai_signal_value"), RetrieverObjectLink( "object_attributes", "Triggers", "trigger_data[__index__].effect_data[__index__].object_attributes" ), RetrieverObjectLink( "variable", "Triggers", "trigger_data[__index__].effect_data[__index__].variable"), RetrieverObjectLink( "timer", "Triggers", "trigger_data[__index__].effect_data[__index__].timer"), RetrieverObjectLink( "facet", "Triggers", "trigger_data[__index__].effect_data[__index__].facet"), RetrieverObjectLink( "play_sound", "Triggers", "trigger_data[__index__].effect_data[__index__].play_sound"), RetrieverObjectLink( "player_color", "Triggers", "trigger_data[__index__].effect_data[__index__].player_color", Support(since=1.40)), RetrieverObjectLink( "message", "Triggers", "trigger_data[__index__].effect_data[__index__].message"), RetrieverObjectLink( "sound_name", "Triggers", "trigger_data[__index__].effect_data[__index__].sound_name"), RetrieverObjectLink( "selected_object_ids", "Triggers", "trigger_data[__index__].effect_data[__index__].selected_object_ids" ), ] def __init__( self, effect_type: int = None, ai_script_goal: int = None, armour_attack_quantity: int = None, armour_attack_class: int = None, quantity: int = None, tribute_list: int = None, diplomacy: int = None, object_list_unit_id: int = None, source_player: int = None, target_player: int = None, technology: int = None, string_id: int = None, display_time: int = None, trigger_id: int = None, location_x: int = None, location_y: int = None, location_object_reference: int = None, area_1_x: int = None, area_1_y: int = None, area_2_x: int = None, area_2_y: int = None, object_group: int = None, object_type: int = None, instruction_panel_position: int = None, attack_stance: int = None, time_unit: int = None, enabled: int = None, food: int = None, wood: int = None, stone: int = None, gold: int = None, item_id: int = None, flash_object: int = None, force_research_technology: int = None, visibility_state: int = None, scroll: int = None, operation: int = None, object_list_unit_id_2: int = None, button_location: int = None, ai_signal_value: int = None, object_attributes: int = None, variable: int = None, timer: int = None, facet: int = None, play_sound: int = None, player_color: int = None, message: str = None, sound_name: str = None, selected_object_ids: List[int] = None, ): raise_if_not_int_subclass( [object_list_unit_id, technology, object_list_unit_id_2]) if selected_object_ids is None: selected_object_ids = [] self.effect_type: int = effect_type self.ai_script_goal: int = ai_script_goal self.armour_attack_quantity: int = armour_attack_quantity self.armour_attack_class: int = armour_attack_class self.quantity: int = quantity self.tribute_list: int = tribute_list self.diplomacy: int = diplomacy self.object_list_unit_id: int = object_list_unit_id self.source_player: int = source_player self.target_player: int = target_player self.technology: int = technology self.string_id: int = string_id self.display_time: int = display_time self.trigger_id: int = trigger_id self.location_x: int = location_x self.location_y: int = location_y self.location_object_reference: int = location_object_reference self.area_1_x: int = area_1_x self.area_1_y: int = area_1_y self.area_2_x: int = area_2_x self.area_2_y: int = area_2_y self.object_group: int = object_group self.object_type: int = object_type self.instruction_panel_position: int = instruction_panel_position self.attack_stance: int = attack_stance self.time_unit: int = time_unit self.enabled: int = enabled self.food: int = food self.wood: int = wood self.stone: int = stone self.gold: int = gold self.item_id: int = item_id self.flash_object: int = flash_object self.force_research_technology: int = force_research_technology self.visibility_state: int = visibility_state self.scroll: int = scroll self.operation: int = operation self.object_list_unit_id_2: int = object_list_unit_id_2 self.button_location: int = button_location self.ai_signal_value: int = ai_signal_value self.object_attributes: int = object_attributes self.variable: int = variable self.timer: int = timer self.facet: int = facet self.play_sound: int = play_sound self.player_color: int = player_color self.message: str = message self.sound_name: str = sound_name self.selected_object_ids: List[int] = selected_object_ids super().__init__() @property def selected_object_ids(self) -> List[int]: return self._selected_object_ids @selected_object_ids.setter def selected_object_ids(self, val: List[int]): val = listify(val) self._selected_object_ids = val def get_content_as_string(self) -> str: if self.effect_type not in effects.attributes: # Unknown effect attributes_list = effects.empty_attributes else: attributes_list = effects.attributes[self.effect_type] return_string = "" for attribute in attributes_list: attribute_value = getattr(self, attribute) if attribute == "effect_type" or attribute_value in [[], [-1], "", " ", -1]: continue return_string += "\t\t\t\t" + attribute + ": " + str( attribute_value) + "\n" if return_string == "": return "\t\t\t\t<< No Attributes >>\n" return return_string
class Condition(AoE2Object): """Object for handling a condition.""" _link_list = [ RetrieverObjectLink("condition_type", "Triggers", "trigger_data[__index__].condition_data[__index__].condition_type"), RetrieverObjectLink("quantity", "Triggers", "trigger_data[__index__].condition_data[__index__].quantity"), RetrieverObjectLink("attribute", "Triggers", "trigger_data[__index__].condition_data[__index__].attribute"), RetrieverObjectLink("unit_object", "Triggers", "trigger_data[__index__].condition_data[__index__].unit_object"), RetrieverObjectLink("next_object", "Triggers", "trigger_data[__index__].condition_data[__index__].next_object"), RetrieverObjectLink("object_list", "Triggers", "trigger_data[__index__].condition_data[__index__].object_list"), RetrieverObjectLink("source_player", "Triggers", "trigger_data[__index__].condition_data[__index__].source_player"), RetrieverObjectLink("technology", "Triggers", "trigger_data[__index__].condition_data[__index__].technology"), RetrieverObjectLink("timer", "Triggers", "trigger_data[__index__].condition_data[__index__].timer"), RetrieverObjectLink("area_1_x", "Triggers", "trigger_data[__index__].condition_data[__index__].area_1_x"), RetrieverObjectLink("area_1_y", "Triggers", "trigger_data[__index__].condition_data[__index__].area_1_y"), RetrieverObjectLink("area_2_x", "Triggers", "trigger_data[__index__].condition_data[__index__].area_2_x"), RetrieverObjectLink("area_2_y", "Triggers", "trigger_data[__index__].condition_data[__index__].area_2_y"), RetrieverObjectLink("object_group", "Triggers", "trigger_data[__index__].condition_data[__index__].object_group"), RetrieverObjectLink("object_type", "Triggers", "trigger_data[__index__].condition_data[__index__].object_type"), RetrieverObjectLink("ai_signal", "Triggers", "trigger_data[__index__].condition_data[__index__].ai_signal"), RetrieverObjectLink("inverted", "Triggers", "trigger_data[__index__].condition_data[__index__].inverted"), RetrieverObjectLink("variable", "Triggers", "trigger_data[__index__].condition_data[__index__].variable"), RetrieverObjectLink("comparison", "Triggers", "trigger_data[__index__].condition_data[__index__].comparison"), RetrieverObjectLink("target_player", "Triggers", "trigger_data[__index__].condition_data[__index__].target_player"), RetrieverObjectLink("xs_function", "Triggers", "trigger_data[__index__].condition_data[__index__].xs_function", Support(since=1.40)), RetrieverObjectLink("unit_ai_action", "Triggers", "trigger_data[__index__].condition_data[__index__].unit_ai_action", Support(since=1.40)), ] def __init__(self, condition_type: int = None, quantity: int = None, attribute: int = None, unit_object: int = None, next_object: int = None, object_list: int = None, source_player: IntEnum = None, technology: IntEnum = None, timer: int = None, area_1_x: int = None, area_1_y: int = None, area_2_x: int = None, area_2_y: int = None, object_group: int = None, object_type: int = None, ai_signal: int = None, inverted: int = None, variable: int = None, comparison: int = None, target_player: IntEnum = None, unit_ai_action: int = None, xs_function: str = None, ): raise_if_not_int_subclass([object_list, technology]) self.condition_type: int = condition_type self.quantity: int = quantity self.attribute: int = attribute self.unit_object: int = unit_object self.next_object: int = next_object self.object_list: int = object_list self.source_player: int = source_player self.technology: int = technology self.timer: int = timer self.area_1_x: int = area_1_x self.area_1_y: int = area_1_y self.area_2_x: int = area_2_x self.area_2_y: int = area_2_y self.object_group: int = object_group self.object_type: int = object_type self.ai_signal: int = ai_signal self.inverted: int = inverted self.variable: int = variable self.comparison: int = comparison self.target_player: int = target_player self.unit_ai_action: int = unit_ai_action self.xs_function: str = xs_function super().__init__() def get_content_as_string(self) -> str: if self.condition_type not in conditions.attributes: attributes_list = conditions.empty_attributes else: attributes_list = conditions.attributes[self.condition_type] return_string = "" for attribute in attributes_list: attr = getattr(self, attribute) if attribute == "condition_type" or attr in [[], [-1], [''], "", " ", -1]: continue return_string += "\t\t\t\t" + attribute + ": " + str(attr) + "\n" if return_string == "": return "\t\t\t\t<< No Attributes >>\n" return return_string
class TriggerManagerDE(TriggerManager): _link_list = [ RetrieverObjectLink("triggers", "Triggers", "trigger_data", process_as_object=Trigger), RetrieverObjectLink("trigger_display_order", "Triggers", "trigger_display_order_array"), RetrieverObjectLink("variables", "Triggers", "variable_data", process_as_object=Variable), ] def __init__(self, triggers: List[Trigger], trigger_display_order: List[int], variables: List[Variable]): self.variables: List[Variable] = variables super().__init__(triggers, trigger_display_order) def add_variable(self, name: str, variable_id: int = -1) -> Variable: """ Adds a variable. Args: name (str): The name for the variable variable_id (int): The ID of the variable. If left empty (default: -1), lowest available value will be used Returns: The newly added Variable """ list_of_var_ids = [var.variable_id for var in self.variables] if variable_id == -1: for i in range(256): if i not in list_of_var_ids: variable_id = i break if variable_id == -1: raise IndexError( f"No variable ID available. All in use? In use: ({list_of_var_ids}/256)" ) if not (0 <= variable_id <= 255): raise ValueError( "Variable ID has to fall between 0 and 255 (incl).") if variable_id in list_of_var_ids: raise ValueError("Variable ID already in use.") new_variable = Variable(variable_id=variable_id, name=name) self.variables.append(new_variable) return new_variable def get_variable(self, variable_id: int = None, variable_name: str = None) -> Variable: if not exclusive_if(variable_id is not None, variable_name is not None): raise ValueError( "Select a variable using either the variable_id or variable_name parameters." ) for variable in self.variables: if variable.variable_id == variable_id or variable.name == variable_name: return variable def get_summary_as_string(self) -> str: return_string = super().get_summary_as_string() return_string += "\nVariables Summary:\n" if len(self.variables) == 0: return_string += "\t<< No Variables >>" longest_variable_name = -1 for variable in self.variables: longest_variable_name = max(longest_variable_name, len(variable.name)) longest_variable_name += 3 for index, variable in enumerate(self.variables): var_name = variable.name name_buffer = " " * (longest_variable_name - len(var_name)) return_string += f"\t{var_name}{name_buffer}[Index: {variable.variable_id}]\n" return return_string def get_content_as_string(self) -> str: return_string = super().get_content_as_string() if len(self.variables) == 0: return_string += "\t<<No Variables>>\n" for variable in self.variables: return_string += f"\t'{variable.name}' [Index: {variable.variable_id}]\n" return return_string
class UnitManager(AoE2Object): """Manager of the everything trigger related.""" _link_list = [ RetrieverObjectLink("units", "Units", "players_units[].units", process_as_object=Unit) ] def __init__(self, units: List[List[Unit]]): self.units = units super().__init__() @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 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 = value def add_unit( self, player: PlayerId, unit_const: int, x: float, y: float, z: float = 0, rotation: float = 0, garrisoned_in_id: int = -1, animation_frame: int = 0, status: int = 2, reference_id: 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 Returns: The Unit created """ if reference_id is None: reference_id = self.get_new_reference_id() unit = Unit( player=player, x=x, y=y, 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, ) self.units[player.value].append(unit) return unit def get_player_units(self, player: 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.value <= 8: raise ValueError("Player must have a value between 0 and 8") return self.units[player.value] def get_all_units(self) -> List[Unit]: units = [] for player_units in self.units: units += player_units return units 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 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[PlayerId] = None, ignore_players: List[PlayerId] = None): """ 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. :Authors: KSneijders (https://github.com/KSneijders/) T-West (https://github.com/twestura/) """ 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.x1 y1 = tile1.y1 x2 = tile2.x2 y2 = tile2.y2 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 change_ownership(self, unit: Unit, to_player: 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) """ for i, player_unit in enumerate(self.units[unit.player.value]): if player_unit == unit: del self.units[unit.player.value][i] self.units[to_player.value].append(unit) unit._player = PlayerId(to_player) return def get_new_reference_id(self) -> int: highest_id = 0 # If no units, default to 0 for player in range(0, 9): for unit in self.units[player]: if highest_id < unit.reference_id: highest_id = unit.reference_id return highest_id + 1 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 (int): The id of the unit. Note that this is NOT a unit constant (So NOT: UnitInfo.ARCHER) unit (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.value].remove(unit)
class MapManager(AoE2Object): """Manager of the everything map related.""" _link_list = [ RetrieverObjectLink("map_width", "Map", "map_width"), RetrieverObjectLink("map_height", "Map", "map_height"), RetrieverObjectLink("terrain", "Map", "terrain_data", process_as_object=TerrainTile), ] def __init__(self, map_width: int, map_height: int, terrain: List[TerrainTile]): self._map_width = map_width self._map_height = map_height self.terrain = terrain super().__init__() @property def map_width(self) -> int: return self._map_width @property def map_height(self) -> int: return self._map_height @property def map_size(self) -> int: if self._map_height == self._map_width: return self._map_height else: raise ValueError( "Map is not a square. Use the attributes 'map_width' and 'map_height' instead." ) @map_size.setter def map_size(self, size: int): new_length = size * size difference = new_length - len(self.terrain) self._map_width = size self._map_height = size if difference < 0: self.terrain = self.terrain[:new_length] elif difference > 0: for _ in range(difference): self.terrain.append( TerrainTile(TerrainId.GRASS_1, elevation=1, layer=-1)) def create_hill(self, x1, y1, x2, y2, elevation) -> None: """ Function that takes the coordinates and the height of a plateau and applies it to the map by also setting the surrounding slopes so that it is smooth. Args: x1 (int): The x coordinate of the west corner y1 (int): The y coordinate of the west corner x2 (int): The x coordinate of the east corner y2 (int): The y coordinate of the east corner elevation (int): The elevation of the map. Default in-game = 1, in-game max = 7. If the given value is over 20 the game camera will 'clip' into the hill. So the in-game camera hovers around the height of 20/21 when fully zoomed in, without Ultra Graphics. :Author: pvallet """ for x in range(max(0, x1 - elevation), min(self.map_size, x2 + elevation)): for y in range(max(0, y1 - elevation), min(self.map_size, y2 + elevation)): if x1 <= x <= x2 and y1 <= y <= y2: intended_elevation = elevation else: distance_to_hill = max(x1 - x, x - x2, y1 - y, y - y2) intended_elevation = elevation - distance_to_hill tile = self.terrain[helper.xy_to_i(x, y, self.map_size)] tile.elevation = max(intended_elevation, tile.elevation)