def _load(self, initial_outfit_parts: Dict[BodyType, int]=None) -> bool:
        target_sim_name = CommonSimNameUtils.get_full_name(self.sim_info)
        self._outfit_data: OutfitData = CommonOutfitUtils.get_outfit_data(self.sim_info, outfit_category_and_index=self._outfit_category_and_index)
        if self._outfit_data is None:
            self.log.error('Missing outfit data for Sim \'{}\' and Outfit Category and Index {}'.format(target_sim_name, self._outfit_category_and_index), throw=True)
            return False
        self._outfit_parts = CommonOutfitUtils.get_outfit_parts(self.sim_info, outfit_category_and_index=self._outfit_category_and_index)
        self._original_outfit_data: FrozenSet[int] = frozenset(self._outfit_parts.items())
        if initial_outfit_parts is not None:
            for (key, value) in initial_outfit_parts.items():
                if not isinstance(key, int) or not isinstance(value, int):
                    self.log.error('\'{}\': outfit_body_parts contains non-integer variables key: {} value: {}.'.format(target_sim_name, key, value))
                    return False

            self._outfit_body_types = list(initial_outfit_parts.keys())
            self._outfit_part_ids = list(initial_outfit_parts.values())
        else:
            if not self._outfit_data.part_ids or not self._outfit_data.body_types:
                self.log.error('\'{}\' is missing outfit parts or body types for Outfit Category and Index {}.'.format(target_sim_name, self._outfit_category_and_index))
                return False
            self._outfit_body_types: List[Union[BodyType, int]] = list(self._outfit_data.body_types)
            self._outfit_part_ids: List[int] = list(self._outfit_data.part_ids)
            if len(self._outfit_body_types) != len(self._outfit_part_ids):
                self.log.error('\'{}\': The number of outfit parts did not match the number of body types for Outfit Category and Index {}.'.format(target_sim_name, self._outfit_category_and_index))
                return False
        return True
    def _setup_dialog_rows(
        self,
        sim_info: SimInfo,
        _dialog: UiOutfitPicker,
        outfit_list: Iterator[Tuple[OutfitCategory, int]]=()
    ):
        self.log.debug('Adding rows.')
        sim_id = CommonSimUtils.get_sim_id(sim_info)
        added_rows = False
        current_outfit = CommonOutfitUtils.get_current_outfit(sim_info)
        for (outfit_category, outfit_index) in outfit_list:
            # noinspection PyTypeChecker
            if not CommonOutfitUtils.has_outfit(sim_info, (outfit_category, outfit_index)) and not CommonOutfitUtils.has_outfit(sim_info, (int(outfit_category), outfit_index)):
                self.log.format_with_message('Sim does not have outfit.', sim=sim_info, outfit_category_and_index=(outfit_category, outfit_index))
                continue
            added_rows = True
            _dialog.add_row(
                OutfitPickerRow(
                    sim_id,
                    outfit_category,
                    outfit_index,
                    is_enable=True,
                    is_selected=(outfit_category, outfit_index) == current_outfit,
                    tag=(outfit_category, outfit_index)
                )
            )

        for row in self.rows:
            _dialog.add_row(row)

        if not added_rows and len(self.rows) == 0:
            raise AssertionError('No rows have been provided. Add rows to the dialog before attempting to display it.')
 def _on_chosen(choice: Union[Tuple[OutfitCategory, int], None],
                outcome: CommonChoiceOutcome) -> None:
     if choice is None or CommonChoiceOutcome.is_error_or_cancel(
             outcome):
         on_completed(False)
         return
     CommonOutfitUtils.set_current_outfit(sim_info, choice)
     on_completed(True)
    def has_cas_part_attached(
            sim_info: SimInfo,
            cas_part_id: int,
            body_type: Union[BodyType, int, None] = BodyType.NONE,
            outfit_category_and_index: Tuple[OutfitCategory,
                                             int] = None) -> bool:
        """has_cas_part_attached(sim_info, cas_part_id, body_type=BodyType.NONE, outfit_category_and_index=None)

        Determine if a Sim has the specified CAS part attached to their outfit.

        :param sim_info: The SimInfo of the Sim to check.
        :type sim_info: SimInfo
        :param cas_part_id: A decimal identifier of the CAS part to locate.
        :type cas_part_id: int
        :param body_type: The BodyType the CAS part will be located at. If no value is provided, it defaults to the BodyType of the CAS part itself. If set to None, the CAS part will be located within any BodyType.
        :type body_type: Union[BodyType, int, None], optional
        :param outfit_category_and_index: The outfit category and index of the Sims outfit to check. Default is the Sims current outfit.
        :type outfit_category_and_index: Union[Tuple[OutfitCategory, int], None], optional
        :return: True, if the Sims outfit contain the specified CAS part. False, if the Sims outfit does not contain the specified CAS part.
        :rtype: bool
        """
        log.format_with_message(
            'Checking if CAS part is attached to Sim.',
            sim=sim_info,
            cas_part_id=cas_part_id,
            body_type=body_type,
            outfit_category_and_index=outfit_category_and_index)
        if body_type == BodyType.NONE:
            body_type = CommonCASUtils.get_body_type_of_cas_part(cas_part_id)
        if outfit_category_and_index is None:
            outfit_category_and_index = CommonOutfitUtils.get_current_outfit(
                sim_info)
        log.format(body_type=body_type,
                   outfit_category_and_index=outfit_category_and_index)
        outfit_parts = CommonOutfitUtils.get_outfit_parts(
            sim_info, outfit_category_and_index=outfit_category_and_index)
        if not outfit_parts:
            log.debug('No body parts found.')
            return False
        log.format_with_message('Found body parts from outfit.',
                                body_parts=outfit_parts)
        if body_type is None:
            log.debug('No BodyType specified.')
            return cas_part_id in outfit_parts.values()
        if body_type not in outfit_parts:
            log.debug('Specified BodyType not found within body parts.')
            return False
        log.debug('BodyType found within Sims outfit parts.')
        attached_cas_part_id = outfit_parts[body_type]
        log.format(attached_cas_part_id=attached_cas_part_id)
        return cas_part_id == attached_cas_part_id
    def get_body_type_cas_part_is_attached_to(
        sim_info: SimInfo,
        cas_part_id: int,
        outfit_category_and_index: Tuple[OutfitCategory, int] = None
    ) -> Union[BodyType, int]:
        """get_body_type_cas_part_is_attached_to(sim_info, cas_part_id, outfit_category_and_index=None)

        Retrieve the BodyType that a CAS part is attached to within a Sims outfit.

        :param sim_info: The SimInfo of the Sim to check.
        :type sim_info: SimInfo
        :param cas_part_id: A decimal identifier of the CAS part to locate.
        :type cas_part_id: int
        :param outfit_category_and_index: The outfit category and index of the Sims outfit to check. If None, the current outfit of the Sim will be used.
        :type outfit_category_and_index: Tuple[OutfitCategory, int], optional
        :return: The BodyType the specified CAS part id is attached to or BodyType.NONE if the CAS part is not found or the Sim does not have body parts for their outfit.
        :rtype: Union[BodyType, int]
        """
        log.format_with_message(
            'Retrieving BodyType for CAS part.',
            sim=sim_info,
            cas_part_id=cas_part_id,
            outfit_category_and_index=outfit_category_and_index)
        if outfit_category_and_index is None:
            outfit_category_and_index = CommonOutfitUtils.get_current_outfit(
                sim_info)
        log.format(cas_part_id=cas_part_id,
                   outfit_category_and_index=outfit_category_and_index)
        outfit_parts = CommonOutfitUtils.get_outfit_parts(
            sim_info, outfit_category_and_index=outfit_category_and_index)
        if not outfit_parts:
            log.debug('No body parts found on Sim!')
            return BodyType.NONE
        log.format_with_message('Found body parts from outfit.',
                                body_parts=outfit_parts)
        for body_type in outfit_parts.keys():
            if cas_part_id != outfit_parts[body_type]:
                continue
            # noinspection PyBroadException
            try:
                body_type = BodyType(body_type)
            except:
                body_type = body_type
            log.format_with_message(
                'Found the BodyType the specified CAS part is attached to.',
                body_type=body_type)
            return body_type
        log.debug('No BodyType was found matching the specified CAS part.')
        return BodyType.NONE
 def on_started(self, interaction_sim: Sim,
                interaction_target: Sim) -> bool:
     target_sim_info = CommonSimUtils.get_sim_info(interaction_target)
     bathing_outfit_category_and_index = (OutfitCategory.BATHING, 0)
     if not CommonOutfitUtils.has_outfit(target_sim_info,
                                         bathing_outfit_category_and_index):
         if not CommonOutfitUtils.generate_outfit(
                 target_sim_info, bathing_outfit_category_and_index):
             self.log.format_with_message(
                 'Failed to generate the bathing outfit for Sim.',
                 sim=target_sim_info)
             return False
     CommonOutfitUtils.set_current_outfit(
         target_sim_info, bathing_outfit_category_and_index)
     return True
    def swap_gender(sim_info: SimInfo, update_gender_options: bool=True) -> bool:
        """swap_gender(sim_info, update_gender_options=True)

        Swap the Gender of a Sim to it's opposite. i.e. Change a Sim from Male to Female or from Female to Male.

        :param sim_info: An instance of a Sim.
        :type sim_info: SimInfo
        :param update_gender_options: If True, gender option traits such as Toilet Usage, Clothing Preference, Pregnancy, and Body Frame will be updated to reflect the vanilla settings for each gender\
        For example, if a Human Sim is swapping from Female to Male, their gender options will be updated to Toilet Standing, Cannot Be Impregnated, Can Impregnate, Mens Wear clothing preference, and Masculine Frame.\
        If False, gender option traits will not be updated.\
        Default is True.
        :type update_gender_options: bool, optional
        :return: True, if the Gender of the Sim was swapped successfully. False, if not.
        :rtype: bool
        """
        from sims4communitylib.utils.sims.common_sim_gender_option_utils import CommonSimGenderOptionUtils
        result = False
        frame = CommonSimGenderOptionUtils.has_masculine_frame(sim_info)
        prefers_menswear = CommonSimGenderOptionUtils.prefers_menswear(sim_info)
        can_impregnate = CommonSimGenderOptionUtils.can_impregnate(sim_info)
        can_be_impregnated = CommonSimGenderOptionUtils.can_be_impregnated(sim_info)
        can_reproduce = CommonSimGenderOptionUtils.can_reproduce(sim_info)
        uses_toilet_standing = CommonSimGenderOptionUtils.uses_toilet_standing(sim_info)
        has_breasts = CommonSimGenderOptionUtils.has_breasts(sim_info)
        saved_outfits = sim_info.save_outfits()
        current_outfit = CommonOutfitUtils.get_current_outfit(sim_info)
        if CommonGenderUtils.is_male(sim_info):
            result = CommonGenderUtils.set_gender(sim_info, Gender.FEMALE)
            if update_gender_options:
                CommonSimGenderOptionUtils.update_gender_options_to_vanilla_female(sim_info)
        elif CommonGenderUtils.is_female(sim_info):
            result = CommonGenderUtils.set_gender(sim_info, Gender.MALE)
            if update_gender_options:
                CommonSimGenderOptionUtils.update_gender_options_to_vanilla_male(sim_info)
        if not update_gender_options:
            CommonSimGenderOptionUtils.update_body_frame(sim_info, frame)
            CommonSimGenderOptionUtils.update_clothing_preference(sim_info, prefers_menswear)
            CommonSimGenderOptionUtils.update_can_impregnate(sim_info, can_impregnate)
            CommonSimGenderOptionUtils.update_can_be_impregnated(sim_info, can_be_impregnated)
            CommonSimGenderOptionUtils.update_can_reproduce(sim_info, can_reproduce)
            CommonSimGenderOptionUtils.update_toilet_usage(sim_info, uses_toilet_standing)
            CommonSimGenderOptionUtils.update_has_breasts(sim_info, has_breasts)
            sim_info.load_outfits(saved_outfits)
            CommonOutfitUtils.resend_outfits(sim_info)
            CommonOutfitUtils.set_current_outfit(sim_info, current_outfit)
        return result
    def apply(self, resend_outfits_after_apply: bool=True, change_sim_to_outfit_after_apply: bool=True, apply_to_all_outfits_in_same_category: bool=False, apply_to_outfit_category_and_index: Tuple[OutfitCategory, int]=None) -> bool:
        """apply(resend_outfits_after_apply=True, change_sim_to_outfit_after_apply=True, apply_to_all_outfits_in_same_category=False, apply_to_outfit_category_and_index=None)

        Apply all changes made to the Outfit.

        :param resend_outfits_after_apply: If set to True, the outfits of the Sim will be re-sent after changes have been applied. Default is True.
        :type resend_outfits_after_apply: bool, optional
        :param change_sim_to_outfit_after_apply: If set to True, the Sim will change to the outfit after the outfit is updated. Default is True.
        :type change_sim_to_outfit_after_apply: bool, optional
        :param apply_to_all_outfits_in_same_category: If set to True, changes will be applied to all Outfits in the same category. If set to False, changes will only be applied to the outfit provided at initialization. Default is False.
        :type apply_to_all_outfits_in_same_category: bool, optional
        :param apply_to_outfit_category_and_index: The OutfitCategory and Index to apply changes to. If set to None, it will be the OutfitCategory and Index provided at initialization. Default is None.
        :type apply_to_outfit_category_and_index: Tuple[OutfitCategory, int], optional
        :return: True, if changes were applied successfully. False, if not.
        :rtype: bool
        """
        sim_name = CommonSimNameUtils.get_full_name(self.sim_info)
        self.log.format_with_message(
            'Applying changes to outfit',
            sim=sim_name,
            resend_outfits=resend_outfits_after_apply,
            change_to_outfit=change_sim_to_outfit_after_apply,
            apply_to_all_outfits_in_same_category=apply_to_all_outfits_in_same_category,
            apply_to_outfit_category_and_index=apply_to_outfit_category_and_index
        )
        apply_to_outfit_category_and_index = apply_to_outfit_category_and_index or self.outfit_category_and_index
        saved_outfits = self.sim_info.save_outfits()
        for saved_outfit in saved_outfits.outfits:
            if int(saved_outfit.category) != int(apply_to_outfit_category_and_index[0]):
                continue

            if apply_to_all_outfits_in_same_category:
                pass
            else:
                # noinspection PyUnresolvedReferences
                sub_outfit_data = self._to_outfit_data(saved_outfit.body_types_list.body_types, saved_outfit.parts.ids)
                if int(saved_outfit.outfit_id) != int(self._outfit_data.outfit_id) or sub_outfit_data != self._original_outfit_data:
                    continue

            saved_outfit.parts = S4Common_pb2.IdList()
            # noinspection PyUnresolvedReferences
            saved_outfit.parts.ids.extend(self._outfit_part_ids)
            saved_outfit.body_types_list = Outfits_pb2.BodyTypesList()
            # noinspection PyUnresolvedReferences
            saved_outfit.body_types_list.body_types.extend(self._outfit_body_types)
            if not apply_to_all_outfits_in_same_category:
                break

        self.sim_info._base.outfits = saved_outfits.SerializeToString()
        if change_sim_to_outfit_after_apply:
            self.sim_info._base.outfit_type_and_index = apply_to_outfit_category_and_index
        else:
            self.sim_info._base.outfit_type_and_index = self._current_outfit_category_and_index
        self.log.format_with_message('Finished flushing outfit changes.', sim=sim_name)
        if resend_outfits_after_apply:
            return CommonOutfitUtils.resend_outfits(self.sim_info)
        return True
    def get_body_type_cas_part_is_attached_to(
        sim_info: SimInfo,
        cas_part_id: int,
        outfit_category_and_index: Tuple[OutfitCategory,
                                         int] = None) -> BodyType:
        """get_body_type_cas_part_is_attached_to(sim_info, cas_part_id, outfit_category_and_index=None)

        Retrieve the BodyType that a cas part is attached to within a Sims outfit.

        :param sim_info: The SimInfo of the Sim to check.
        :type sim_info: SimInfo
        :param cas_part_id: A decimal identifier of the CAS part to locate.
        :type cas_part_id: int
        :param outfit_category_and_index: The outfit category and index of the Sims outfit to check. Default is the Sims current outfit.
        :type outfit_category_and_index: Tuple[OutfitCategory, int], optional
        :return: The BodyType the specified cas part id is attached to or -1 if the cas part is not found.
        :rtype: BodyType
        """
        log.format_with_message(
            'Retrieving BodyType for cas part.',
            sim=sim_info,
            cas_part_id=cas_part_id,
            outfit_category_and_index=outfit_category_and_index)
        if outfit_category_and_index is None:
            outfit_category_and_index = CommonOutfitUtils.get_current_outfit(
                sim_info)
        log.format(cas_part_id=cas_part_id,
                   outfit_category_and_index=outfit_category_and_index)
        outfit_parts = CommonOutfitUtils.get_outfit_parts(
            sim_info, outfit_category_and_index=outfit_category_and_index)
        if not outfit_parts:
            log.debug('No body parts found.')
            return BodyType.NONE
        log.format_with_message('Found body parts from outfit.',
                                body_parts=outfit_parts)
        for body_type in outfit_parts.keys():
            if cas_part_id != outfit_parts[body_type]:
                continue
            log.format_with_message('Found BodyType.',
                                    body_type=BodyType(body_type))
            return BodyType(body_type)
        log.debug('No BodyType found matching the cas part.')
        return BodyType.NONE
Beispiel #10
0
    def is_wearing_towel(sim_info: SimInfo) -> bool:
        """is_wearing_towel(sim_info)

        Determine if a Sim is wearing a towel.

        ..warning:: Obsolete: Use :func:`~is_wearing_towel` in :class:`.CommonOutfitUtils` instead.

        """
        from sims4communitylib.utils.cas.common_outfit_utils import CommonOutfitUtils
        return CommonOutfitUtils.is_wearing_towel(sim_info)
    def get_cas_part_id_at_body_type(
            sim_info: SimInfo,
            body_type: Union[BodyType, int],
            outfit_category_and_index: Tuple[OutfitCategory,
                                             int] = None) -> int:
        """get_cas_part_id_at_body_type(sim_info, body_type, outfit_category_and_index=None)

        Retrieve the CAS part identifier attached to the specified BodyType within a Sims outfit.

        :param sim_info: The SimInfo of the Sim to check.
        :type sim_info: SimInfo
        :param body_type: The BodyType to check.
        :type body_type: Union[BodyType, int]
        :param outfit_category_and_index: The outfit category and index of the Sims outfit to check. Default is the Sims current outfit.
        :type outfit_category_and_index: Tuple[OutfitCategory, int], optional
        :return: The CAS part identifier attached to the specified BodyType or -1 if the BodyType is not found.
        :rtype: int
        """
        log.format_with_message(
            'Checking if CAS part is attached to Sim.',
            sim=sim_info,
            body_type=body_type,
            outfit_category_and_index=outfit_category_and_index)
        if outfit_category_and_index is None:
            outfit_category_and_index = CommonOutfitUtils.get_current_outfit(
                sim_info)
        log.format(body_type=body_type,
                   outfit_category_and_index=outfit_category_and_index)
        outfit_parts = CommonOutfitUtils.get_outfit_parts(
            sim_info, outfit_category_and_index=outfit_category_and_index)
        if not outfit_parts:
            log.debug('No body_parts found on Sim.')
            return -1
        log.format_with_message('Found body parts from outfit.',
                                body_parts=outfit_parts)
        if body_type not in outfit_parts:
            log.debug(
                'The specified BodyType was not found within the Sims outfit.')
            return -1
        log.debug(
            'BodyType has been found within Sims outfit parts. Returning the CAS part belonging to it.'
        )
        return outfit_parts[body_type]
 def __init__(self, sim_info: SimInfo, outfit_category_and_index: Tuple[OutfitCategory, int]=None, initial_outfit_parts: Dict[BodyType, int]=None, mod_identity: CommonModIdentity=None):
     super().__init__()
     self._mod_identity = mod_identity
     self._sim_info: SimInfo = sim_info
     self._current_outfit_category_and_index = CommonOutfitUtils.get_current_outfit(sim_info)
     self._outfit_category_and_index: Tuple[OutfitCategory, int] = outfit_category_and_index or self._current_outfit_category_and_index
     self._outfit_data: OutfitData = None
     self._outfit_parts: Dict[BodyType, int] = None
     self._original_outfit_data: FrozenSet[int] = None
     self._outfit_body_types: List[Union[BodyType, int]] = None
     self._outfit_part_ids: List[int] = None
     self._load(initial_outfit_parts=initial_outfit_parts)
Beispiel #13
0
    def _regenerate_every_outfit(self, sim_info: SimInfo) -> bool:
        result = False
        for occult_base_sim_info in CommonOccultUtils.get_sim_info_for_all_occults_gen(
                sim_info, (OccultType.MERMAID, )):
            for outfit_category in CommonOutfitUtils.get_all_outfit_categories(
            ):
                for outfit_index in range(
                        get_maximum_outfits_for_category(outfit_category)):
                    if not CommonOutfitUtils.has_outfit(
                            occult_base_sim_info,
                        (outfit_category, outfit_index)):
                        continue

                    if CommonOutfitUtils.generate_outfit(
                            occult_base_sim_info,
                            outfit_category_and_index=(outfit_category,
                                                       outfit_index)):
                        result = True

        if result:
            CommonOutfitUtils.update_outfits(sim_info)
        return result
 def on_test(cls, interaction_sim: Sim, interaction_target: Any,
             interaction_context: InteractionContext,
             **kwargs) -> TestResult:
     if interaction_target is None or not CommonTypeUtils.is_sim_or_sim_info(
             interaction_target):
         cls.get_log().debug('Failed, Target is not a Sim.')
         return TestResult.NONE
     target_sim_info = CommonSimUtils.get_sim_info(interaction_target)
     if not S4CMSettingUtils.is_sim_allowed_to_perform_adult_sim_operations(
             target_sim_info):
         cls.get_log().format_with_message(
             'Failed, Target Sim is not enabled for interactions.',
             target=target_sim_info)
         return TestResult.NONE
     if CommonOutfitUtils.is_wearing_bathing_outfit(target_sim_info):
         cls.get_log().format_with_message(
             'Failed, Target Sim is already wearing their bathing outfit.',
             target=target_sim_info)
         return TestResult.NONE
     cls.get_log().format_with_message(
         'Success, can Change Outfit To Nude.', target=target_sim_info)
     return TestResult.TRUE
Beispiel #15
0
 def is_wearing_towel(sim_info: SimInfo) -> bool:
     """ Obsolete: Please use CommonOutfitUtils.is_wearing_towel """
     from sims4communitylib.utils.cas.common_outfit_utils import CommonOutfitUtils
     return CommonOutfitUtils.is_wearing_towel(sim_info)
Beispiel #16
0
 def _update_gender_options(self, sim_info: SimInfo):
     update_outfits = False
     if self._setting_utils.force_all_sims_to_male(
     ) and not CommonGenderUtils.is_male(sim_info):
         CommonGenderUtils.swap_gender(sim_info,
                                       update_gender_options=False)
         update_outfits = True
     elif self._setting_utils.force_all_sims_to_female(
     ) and not CommonGenderUtils.is_female(sim_info):
         CommonGenderUtils.swap_gender(sim_info,
                                       update_gender_options=False)
         update_outfits = True
     if CommonGenderUtils.is_male(sim_info):
         if CommonSpeciesUtils.is_pet(sim_info):
             if self._setting_utils.all_male_options.can_reproduce(
             ) and not CommonSimGenderOptionUtils.can_reproduce(sim_info):
                 CommonSimGenderOptionUtils.update_can_reproduce(
                     sim_info, True)
             if self._setting_utils.all_male_options.cannot_reproduce(
             ) and not CommonSimGenderOptionUtils.can_not_reproduce(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_reproduce(
                     sim_info, False)
         elif CommonSpeciesUtils.is_human(sim_info):
             if self._setting_utils.all_male_options.use_toilet_standing(
             ) and not CommonSimGenderOptionUtils.uses_toilet_standing(
                     sim_info):
                 CommonSimGenderOptionUtils.update_toilet_usage(
                     sim_info, True)
                 update_outfits = True
             if self._setting_utils.all_male_options.use_toilet_sitting(
             ) and not CommonSimGenderOptionUtils.uses_toilet_sitting(
                     sim_info):
                 CommonSimGenderOptionUtils.update_toilet_usage(
                     sim_info, False)
                 update_outfits = True
             if self._setting_utils.all_male_options.prefer_menswear(
             ) and not CommonSimGenderOptionUtils.prefers_menswear(
                     sim_info):
                 CommonSimGenderOptionUtils.update_clothing_preference(
                     sim_info, True)
             if self._setting_utils.all_male_options.prefer_womenswear(
             ) and not CommonSimGenderOptionUtils.prefers_womenswear(
                     sim_info):
                 CommonSimGenderOptionUtils.update_clothing_preference(
                     sim_info, False)
             if self._setting_utils.all_male_options.force_masculine_body_frame(
             ) and not CommonSimGenderOptionUtils.has_masculine_frame(
                     sim_info):
                 CommonSimGenderOptionUtils.update_body_frame(
                     sim_info, True)
             if self._setting_utils.all_male_options.force_feminine_body_frame(
             ) and not CommonSimGenderOptionUtils.has_feminine_frame(
                     sim_info):
                 CommonSimGenderOptionUtils.update_body_frame(
                     sim_info, False)
             if self._setting_utils.all_male_options.can_impregnate(
             ) and not CommonSimGenderOptionUtils.can_impregnate(sim_info):
                 CommonSimGenderOptionUtils.update_can_impregnate(
                     sim_info, True)
             if self._setting_utils.all_male_options.cannot_impregnate(
             ) and not CommonSimGenderOptionUtils.can_not_impregnate(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_impregnate(
                     sim_info, False)
             if self._setting_utils.all_male_options.can_be_impregnated(
             ) and not CommonSimGenderOptionUtils.can_be_impregnated(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_be_impregnated(
                     sim_info, True)
             if self._setting_utils.all_male_options.cannot_be_impregnated(
             ) and not CommonSimGenderOptionUtils.can_not_be_impregnated(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_be_impregnated(
                     sim_info, False)
             if self._setting_utils.all_male_options.force_breasts_on(
             ) and not CommonSimGenderOptionUtils.has_breasts(sim_info):
                 CommonSimGenderOptionUtils.update_has_breasts(
                     sim_info, True)
                 update_outfits = True
             if self._setting_utils.all_male_options.force_breasts_off(
             ) and CommonSimGenderOptionUtils.has_breasts(sim_info):
                 CommonSimGenderOptionUtils.update_has_breasts(
                     sim_info, False)
                 update_outfits = True
     elif CommonGenderUtils.is_female(sim_info):
         if CommonSpeciesUtils.is_pet(sim_info):
             if self._setting_utils.all_female_options.can_reproduce(
             ) and not CommonSimGenderOptionUtils.can_reproduce(sim_info):
                 CommonSimGenderOptionUtils.update_can_reproduce(
                     sim_info, True)
             if self._setting_utils.all_female_options.cannot_reproduce(
             ) and not CommonSimGenderOptionUtils.can_not_reproduce(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_reproduce(
                     sim_info, False)
         elif CommonSpeciesUtils.is_human(sim_info):
             if self._setting_utils.all_female_options.use_toilet_standing(
             ) and not CommonSimGenderOptionUtils.uses_toilet_standing(
                     sim_info):
                 CommonSimGenderOptionUtils.update_toilet_usage(
                     sim_info, True)
                 update_outfits = True
             if self._setting_utils.all_female_options.use_toilet_sitting(
             ) and not CommonSimGenderOptionUtils.uses_toilet_sitting(
                     sim_info):
                 CommonSimGenderOptionUtils.update_toilet_usage(
                     sim_info, False)
                 update_outfits = True
             if self._setting_utils.all_female_options.prefer_menswear(
             ) and not CommonSimGenderOptionUtils.prefers_menswear(
                     sim_info):
                 CommonSimGenderOptionUtils.update_clothing_preference(
                     sim_info, True)
             if self._setting_utils.all_female_options.prefer_womenswear(
             ) and not CommonSimGenderOptionUtils.prefers_womenswear(
                     sim_info):
                 CommonSimGenderOptionUtils.update_clothing_preference(
                     sim_info, False)
             if self._setting_utils.all_female_options.force_masculine_body_frame(
             ) and not CommonSimGenderOptionUtils.has_masculine_frame(
                     sim_info):
                 CommonSimGenderOptionUtils.update_body_frame(
                     sim_info, True)
             if self._setting_utils.all_female_options.force_feminine_body_frame(
             ) and not CommonSimGenderOptionUtils.has_feminine_frame(
                     sim_info):
                 CommonSimGenderOptionUtils.update_body_frame(
                     sim_info, False)
             if self._setting_utils.all_female_options.can_impregnate(
             ) and not CommonSimGenderOptionUtils.can_impregnate(sim_info):
                 CommonSimGenderOptionUtils.update_can_impregnate(
                     sim_info, True)
             if self._setting_utils.all_female_options.cannot_impregnate(
             ) and not CommonSimGenderOptionUtils.can_not_impregnate(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_impregnate(
                     sim_info, False)
             if self._setting_utils.all_female_options.can_be_impregnated(
             ) and not CommonSimGenderOptionUtils.can_be_impregnated(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_be_impregnated(
                     sim_info, True)
             if self._setting_utils.all_female_options.cannot_be_impregnated(
             ) and not CommonSimGenderOptionUtils.can_not_be_impregnated(
                     sim_info):
                 CommonSimGenderOptionUtils.update_can_be_impregnated(
                     sim_info, False)
             if self._setting_utils.all_female_options.force_breasts_on(
             ) and not CommonSimGenderOptionUtils.has_breasts(sim_info):
                 CommonSimGenderOptionUtils.update_has_breasts(
                     sim_info, True)
                 update_outfits = True
             if self._setting_utils.all_female_options.force_breasts_off(
             ) and CommonSimGenderOptionUtils.has_breasts(sim_info):
                 CommonSimGenderOptionUtils.update_has_breasts(
                     sim_info, False)
                 update_outfits = True
     if update_outfits:
         CommonOutfitUtils.update_outfits(sim_info)
    def attach_cas_part_to_sim(
        sim_info: SimInfo,
        cas_part_id: int,
        body_type: Union[BodyType, int] = BodyType.NONE,
        outfit_category_and_index: Union[Tuple[OutfitCategory, int],
                                         None] = None
    ) -> bool:
        """attach_cas_part_to_sim(sim_info, cas_part_id, body_type=BodyType.NONE, outfit_category_and_index=None)

        Add a CAS part at the specified BodyType to the Sims outfit.

        :param sim_info: The SimInfo of a Sim to add the CAS part to.
        :type sim_info: SimInfo
        :param cas_part_id: The decimal identifier of a CAS part to attach to the Sim.
        :type cas_part_id: int
        :param body_type: The BodyType the CAS part will be attached to. If no value is provided or it is None, the BodyType of the CAS part itself will be used.
        :type body_type: Union[BodyType, int], optional
        :param outfit_category_and_index: The outfit category and index of the Sims outfit to modify. If no value is provided, the Sims current outfit will be used.
        :type outfit_category_and_index: Union[Tuple[OutfitCategory, int], None], optional
        :return: True if the CAS part was successfully attached to the Sim. False if the CAS part was not successfully attached to the Sim.
        :rtype: bool
        """
        log.format_with_message(
            'Attempting to attach CAS part to Sim',
            sim=sim_info,
            cas_part_id=cas_part_id,
            body_type=body_type,
            outfit_category_and_index=outfit_category_and_index)
        if cas_part_id == -1 or cas_part_id is None:
            raise RuntimeError('No cas_part_id was provided.')
        log.debug('Pre-saving outfits.')
        saved_outfits = sim_info.save_outfits()
        if outfit_category_and_index is None:
            outfit_category_and_index = CommonOutfitUtils.get_current_outfit(
                sim_info)
        log.format_with_message(
            'Using outfit category and index.',
            outfit_category_and_index=outfit_category_and_index)
        outfit_data = CommonOutfitUtils.get_outfit_data(
            sim_info, outfit_category_and_index=outfit_category_and_index)
        outfit_identifier = frozenset(
            dict(zip(list(outfit_data.body_types),
                     list(outfit_data.part_ids))).items())
        if body_type is None or body_type == BodyType.NONE:
            body_type = CommonCASUtils.get_body_type_of_cas_part(cas_part_id)
        log.format_with_message('Using body_type', body_type=body_type)
        for outfit in saved_outfits.outfits:
            log.format_with_message('Attempting to update outfit.',
                                    outfit=outfit)
            # noinspection PyUnresolvedReferences
            _outfit_identifier = frozenset(
                dict(
                    zip(list(outfit.body_types_list.body_types),
                        list(outfit.parts.ids))).items())
            if int(outfit.category) != int(
                    outfit_category_and_index[0]
            ) or outfit.outfit_id != outfit_data.outfit_id or _outfit_identifier != outfit_identifier:
                log.format_with_message(
                    'Outfit is not the outfit we want to update, skipping.',
                    outfit_id=outfit.outfit_id,
                    outfit_category=outfit.category)
                continue
            log.debug('Updating outfit.')
            # noinspection PyUnresolvedReferences
            previous_cas_parts_list = list(outfit.parts.ids)
            if cas_part_id not in previous_cas_parts_list:
                log.format_with_message('Adding CAS part id.',
                                        cas_part_id=cas_part_id)
                previous_cas_parts_list.append(cas_part_id)
            outfit.parts = S4Common_pb2.IdList()
            # noinspection PyUnresolvedReferences
            outfit.parts.ids.extend(previous_cas_parts_list)
            # noinspection PyUnresolvedReferences
            previous_body_types_list = list(outfit.body_types_list.body_types)
            if body_type not in previous_body_types_list:
                log.format_with_message('Adding BodyType.',
                                        body_type=body_type)
                previous_body_types_list.append(body_type)
            outfit.body_types_list = Outfits_pb2.BodyTypesList()
            # noinspection PyUnresolvedReferences
            outfit.body_types_list.body_types.extend(previous_body_types_list)
            log.debug('Done updating outfit.')
        log.debug('Done updating outfits.')
        sim_info._base.outfits = saved_outfits.SerializeToString()
        sim_info._base.outfit_type_and_index = outfit_category_and_index
        log.debug('Resending outfits.')
        CommonOutfitUtils.resend_outfits(sim_info)
        log.debug('Done adding CAS part to Sim.')
        return True