class NarrativeAwareComponent(Component,
                              HasTunableFactory,
                              AutoFactoryInit,
                              component_name=NARRATIVE_AWARE_COMPONENT):
    FACTORY_TUNABLES = {
        'narrative_state_mapping':
        TunableMapping(
            description=
            '\n            A tunable mapping linking a narrative to the states the component\n            owner should have.\n            ',
            key_type=TunableReference(
                description=
                '\n                The narrative we are interested in.\n                ',
                manager=services.get_instance_manager(Types.NARRATIVE)),
            value_type=TunableList(
                description=
                '\n                A tunable list of states to apply to the owning object of\n                this component when this narrative is active.\n                ',
                tunable=TunableStateValueReference(pack_safe=True)))
    }

    def on_add(self):
        self.on_narratives_set(services.narrative_service().active_narratives)

    def on_finalize_load(self):
        self.on_narratives_set(services.narrative_service().active_narratives)

    def on_narratives_set(self, narratives):
        for narrative in narratives:
            if narrative in self.narrative_state_mapping:
                for state_value in self.narrative_state_mapping[narrative]:
                    self.owner.set_state(state_value.state, state_value)
class TimeOfDayComponent(Component,
                         HasTunableFactory,
                         component_name=types.TIME_OF_DAY_COMPONENT):
    __qualname__ = 'TimeOfDayComponent'
    DAILY_REPEAT = date_and_time.create_time_span(hours=24)
    FACTORY_TUNABLES = {
        'state_changes':
        TunableMapping(
            description=
            '\n            A mapping from state to times of the day when the state should be \n            set to a tuned value.\n            ',
            key_type=TunableStateTypeReference(
                description='The state to be set.'),
            value_type=TunableList(
                description='List of times to modify the state at.',
                tunable=TunableTuple(start_time=TunableRange(
                    float,
                    0,
                    description=
                    'The start time (24 hour clock time) for the Day_Time state.',
                    minimum=0,
                    maximum=24),
                                     value=TunableStateValueReference(
                                         description='New state value.'))))
    }

    def __init__(self, owner, *, state_changes):
        super().__init__(owner)
        self.state_changes = state_changes
        self.alarm_handles = []

    def _add_alarm(self, cur_state, game_clock, state, change):
        time_to_day = game_clock.time_until_hour_of_day(change.start_time)

        def change_state(_):
            self.owner.set_state(state, change.value)

        self.alarm_handles.append(
            alarms.add_alarm(self.owner,
                             time_to_day,
                             change_state,
                             repeating=True,
                             repeating_time_span=self.DAILY_REPEAT))
        if cur_state is None or time_to_day > cur_state[0]:
            return (time_to_day, change.value)
        return cur_state

    def on_add(self):
        game_clock_service = services.game_clock_service()
        for (state, changes) in self.state_changes.items():
            current_state = None
            for change in changes:
                current_state = self._add_alarm(current_state,
                                                game_clock_service, state,
                                                change)
            while current_state is not None:
                self.owner.set_state(state, current_state[1])

    def on_remove(self):
        for handle in self.alarm_handles:
            alarms.cancel_alarm(handle)
Example #3
0
class ObjectStateHelper(AutoFactoryInit, HasTunableSingletonFactory):
    FACTORY_TUNABLES = {
        'object_target':
        TunableObjectTargetVariant(
            description=
            '\n            Define the set of objects that this interaction is applied to.\n            '
        ),
        'object_tags':
        TunableTags(
            description=
            '\n            Find all of the objects based on these tags.\n            ',
            filter_prefixes=('func', )),
        'desired_state':
        TunableStateValueReference(
            description=
            '\n            State that will be set to the objects.\n            ',
            pack_safe=True),
        'tests':
        TunableTestSet(
            description=
            "\n            If pass these tests, the object's state will be changed to\n            Desired State.\n            "
        )
    }

    def execute_helper(self, interaction):
        if self.desired_state is not None:
            objects = list(services.object_manager().get_objects_with_tags_gen(
                *self.object_tags))
            for obj in self.object_target.get_object_target_gen(
                    interaction, objects):
                resolver = SingleObjectResolver(obj)
                if self.tests.run_tests(resolver):
                    obj.set_state(self.desired_state.state, self.desired_state)
Example #4
0
 def __init__(self, *args, **kwargs):
     super().__init__(
         state=TunableStateValueReference(
             description=
             '\n                An state that we want to set the object to.\n                '
         ),
         description=
         '\n                Change the state of an object to the tuned state.\n                '
     )
Example #5
0
class StereoComponent(Component,
                      HasTunableFactory,
                      AutoFactoryInit,
                      component_name=types.STEREO_COMPONENT):
    FACTORY_TUNABLES = {
        'channel_state':
        TunableStateTypeReference(
            description=
            '\n            The state used to populate the radio stations'),
        'off_state':
        TunableStateValueReference(
            description=
            '\n            The channel that represents the off state.'),
        'listen_affordances':
        TunableList(
            description=
            '\n            An ordered list of affordances that define "listening" to this\n            stereo. The first succeeding affordance is used.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
                                     pack_safe=True)),
        'play_on_active_sim_only':
        Tunable(
            description=
            '\n            If enabled, and audio target is Sim, the audio will only be \n            played on selected Sim. Otherwise it will be played regardless \n            Sim is selected or not.\n            \n            If audio target is Object, always set this to False. Otherwise\n            the audio will never be played.\n            \n            ex. This will be useful for Earbuds where we want to hear the\n            music only when the Sim is selected.\n            \n            This is passed down to the audio state when it is triggered, and thus\n            will overwrite any tuning on the state value.\n            ',
            tunable_type=bool,
            default=False),
        'immediate':
        Tunable(
            description=
            '\n            If checked, this audio will be triggered immediately, nothing\n            will block.\n            \n            ex. Earbuds audio will be played immediately while \n            the Sim is routing or animating.\n            \n            This is passed down to the audio state when it is triggered, and thus\n            will overwrite any tuning on the state value.\n            ',
            tunable_type=bool,
            default=False)
    }

    def is_stereo_turned_on(self):
        current_channel = self.owner.get_state(self.channel_state)
        return current_channel != self.off_state

    def get_available_picker_channel_states(self, context):
        for client_state in self.owner.get_client_states(self.channel_state):
            if client_state.show_in_picker:
                if client_state.test_channel(self.owner, context):
                    yield client_state

    def component_potential_interactions_gen(self, context, **kwargs):
        current_channel = self.owner.get_state(self.channel_state)
        if current_channel != self.off_state:
            for listen_affordance in self.listen_affordances:
                yield from listen_affordance.potential_interactions(
                    self.owner,
                    context,
                    required_station=current_channel,
                    off_state=self.off_state,
                    **kwargs)
Example #6
0
    class _SpawnFiremeterStateBased(HasTunableSingletonFactory,
                                    AutoFactoryInit):
        FACTORY_TUNABLES = {
            'state_values':
            TunableMapping(
                description=
                '\n                A mapping of state value to cap. If the object has the specified\n                state set, the associated value is used as a cap. The order is\n                evaluated arbitrarily, so avoid tuning states that are not\n                mutually exclusive.\n                \n                If the object has no state set, no firemeter cap applies.\n                ',
                key_type=TunableStateValueReference(pack_safe=True),
                value_type=Tunable(tunable_type=int, default=1))
        }

        def __call__(self, obj):
            for (state_value, value) in self.state_values.items():
                if obj.state_value_active(state_value):
                    return value
Example #7
0
 def __init__(
         self,
         description="If the target object isn't in the given state, use an asm and set that object's state.",
         **kwargs):
     super().__init__(
         value=TunableStateValueReference(
             description='The value to require'),
         xevt_id=Tunable(
             int,
             None,
             description=
             "An xevt on which to change the state's value (optional)."),
         animation_ref=TunableAnimationReference(),
         description=description,
         **kwargs)
Example #8
0
class LaundryTuning:
    GENERATE_CLOTHING_PILE = TunableTuple(description='\n        The tunable to generate clothing pile on the lot. This will be called\n        when we find laundry hero objects on the lot and there is no hamper\n        available.\n        ', loot_to_apply=TunableReference(description='\n            Loot to apply for generating clothing pile.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True), naked_outfit_category=TunableSet(description="\n            Set of outfits categories which is considered naked.\n            When Sim switches FROM these outfits, it won't generate the pile.\n            When Sim switches TO these outfits, it won't apply laundry reward\n            or punishment.\n            ", tunable=TunableEnumEntry(tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY, invalid_enums=(OutfitCategory.CURRENT_OUTFIT,))), no_pile_outfit_category=TunableSet(description="\n            Set of outfits categories which will never generate the pile.\n            When Sim switches FROM or TO these outfits, it won't generate the\n            pile.\n            \n            Laundry reward or punishment will still be applied to the Sim when \n            switching FROM or TO these outfits.\n            ", tunable=TunableEnumEntry(tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY, invalid_enums=(OutfitCategory.CURRENT_OUTFIT,))), no_pile_interaction_tag=TunableEnumWithFilter(description='\n            If interaction does spin clothing change and has this tag, it will\n            generate no clothing pile.\n            ', tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('interaction',)))
    HAMPER_OBJECT_TAGS = TunableTags(description='\n        Tags that considered hamper objects.\n        ', filter_prefixes=('func',))
    LAUNDRY_HERO_OBJECT_TAGS = TunableTags(description='\n        Tags of laundry hero objects. Placing any of these objects on the lot\n        will cause the service to generate clothing pile for each Sims on the\n        household after spin clothing change.\n        ', filter_prefixes=('func',))
    NOT_DOING_LAUNDRY_PUNISHMENT = TunableTuple(description='\n        If no Sim in the household unload completed laundry in specific\n        amount of time, the negative loot will be applied to Sim household \n        on spin clothing change to engage them doing laundry.\n        ', timeout=TunableSimMinute(description="\n            The amount of time in Sim minutes, since the last time they're \n            finishing laundry, before applying the loot.\n            ", default=2880, minimum=1), loot_to_apply=TunableReference(description='\n            Loot defined here will be applied to the Sim in the household\n            on spin clothing change if they are not doing laundry for \n            a while.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True))
    PUT_AWAY_FINISHED_LAUNDRY = TunableTuple(description='\n        The tunable to update laundry service on Put Away finished laundry\n        interaction.\n        ', interaction_tag=TunableEnumWithFilter(description='\n            Tag that represent the put away finished laundry interaction which \n            will update Laundry Service data.\n            ', tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('interaction',)), laundry_condition_states=TunableTuple(description='\n            This is the state type of completed laundry object condition \n            which will aggregate the data to the laundry service.\n            ', condition_states=TunableList(description='\n                A list of state types to be stored on laundry service.\n                ', tunable=TunableStateTypeReference(pack_safe=True), unique_entries=True), excluded_states=TunableList(description='\n                A list of state values of Condition States which will not \n                be added to the laundry service.\n                ', tunable=TunableStateValueReference(pack_safe=True), unique_entries=True)), laundry_condition_timeout=TunableSimMinute(description='\n            The amount of time in Sim minutes that the individual laundry\n            finished conditions will be kept in the laundry conditions \n            aggregate data.\n            ', default=1440, minimum=0), conditions_and_rewards_map=TunableMapping(description='\n            Mapping of laundry conditions and loot rewards.\n            ', key_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.OBJECT_STATE), pack_safe=True), value_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True)))
    PUT_CLOTHING_PILE_ON_HAMPER = TunableTuple(description='\n        The Tunable to directly put generated clothing pile in the hamper.\n        ', chance=TunablePercent(description='\n            The chance that a clothing pile will be put directly in the hamper. \n            Tune the value in case putting clothing pile in hamper every \n            spin-outfit-change feeling excessive.\n            ', default=100), clothing_pile=TunableTuple(description="\n            Clothing pile object that will be created and put into the hamper \n            automatically. \n            \n            You won't see the object on the lot since it will go directly to \n            the hamper. We create it because we need to transfer all of the \n            commodities data and average the values into the hamper precisely.\n            ", definition=TunablePackSafeReference(description='\n                Reference to clothing pile object definition.\n                ', manager=services.definition_manager()), initial_states=TunableList(description='\n                A list of states to apply to the clothing pile as soon as it \n                is created.\n                ', tunable=TunableTuple(description='\n                    The state to apply and optional to decide if the state \n                    should be applied.\n                    ', state=TunableStateValueReference(pack_safe=True), tests=TunableTestSet()))), full_hamper_state=TunableStateValueReference(description='\n            The state of full hamper which make the hamper is unavailable to \n            add new clothing pile in it.\n            ', pack_safe=True), loots_to_apply=TunableList(description='\n            Loots to apply to the hamper when clothing pile is being put.\n            ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True)), tests=TunableTestSet(description='\n            The test to run on the Sim that must pass in order for putting\n            clothing pile automatically to the hamper. These tests will only \n            be run when we have available hamper on the lot.\n            '))
Example #9
0
class Photography:
    SMALL_PORTRAIT_OBJ_DEF = TunablePackSafeReference(
        description=
        '\n        Object definition for a small portrait photo.\n        ',
        manager=services.definition_manager())
    SMALL_LANDSCAPE_OBJ_DEF = TunablePackSafeReference(
        description=
        '\n        Object definition for a small landscape photo.\n        ',
        manager=services.definition_manager())
    MEDIUM_PORTRAIT_OBJ_DEF = TunablePackSafeReference(
        description=
        '\n        Object definition for a medium portrait photo.\n        ',
        manager=services.definition_manager())
    MEDIUM_LANDSCAPE_OBJ_DEF = TunablePackSafeReference(
        description=
        '\n        Object definition for a medium landscape photo.\n        ',
        manager=services.definition_manager())
    LARGE_PORTRAIT_OBJ_DEF = TunablePackSafeReference(
        description=
        '\n        Object definition for a large portrait photo.\n        ',
        manager=services.definition_manager())
    LARGE_LANDSCAPE_OBJ_DEF = TunablePackSafeReference(
        description=
        '\n        Object definition for a large landscape photo.\n        ',
        manager=services.definition_manager())
    PAINTING_INTERACTION_TAG = TunableEnumEntry(
        description=
        '\n        Tag to specify a painting interaction.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID)
    PHOTOGRAPHY_LOOT_LIST = TunableList(
        description=
        '\n        A list of loot operations to apply to the photographer when photo mode exits.\n        ',
        tunable=TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.ACTION),
                                 class_restrictions=('LootActions', ),
                                 pack_safe=True))
    FAIL_PHOTO_QUALITY_RANGE = TunableInterval(
        description=
        '\n        The random quality statistic value that a failure photo will be\n        given between the min and max tuned values.\n        ',
        tunable_type=int,
        default_lower=0,
        default_upper=100)
    BASE_PHOTO_QUALITY_MAP = TunableMapping(
        description=
        '\n        The mapping of CameraQuality value to an interval of quality values\n        that will be used to asign a random base quality value to a photo\n        as it is created.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The CameraQuality value. If this photo has this CameraQuality,\n            value, then a random quality between the min value and max value\n            will be assigned to the photo.\n            ',
            tunable_type=CameraQuality,
            default=CameraQuality.CHEAP),
        value_type=TunableInterval(
            description=
            '\n            The range of base quality values from which a random value will be\n            given to the photo.\n            ',
            tunable_type=int,
            default_lower=1,
            default_upper=100))
    QUALITY_MODIFIER_PER_SKILL_LEVEL = Tunable(
        description=
        '\n        For each level of skill in Photography, this amount will be added to\n        the quality statistic.\n        ',
        tunable_type=float,
        default=0)
    PHOTO_VALUE_MODIFIER_MAP = TunableMapping(
        description=
        '\n        The mapping of state values to Simoleon value modifiers.\n        The final value of a photo is decided based on its\n        current value multiplied by the sum of all modifiers for\n        states that apply to the photo. All modifiers are\n        added together first, then the sum will be multiplied by\n        the current price.\n        ',
        key_type=TunableStateValueReference(
            description=
            '\n            The quality state values. If this photo has this state,\n            then a random modifier between min_value and max_value\n            will be multiplied to the current price.'
        ),
        value_type=TunableInterval(
            description=
            '\n            The maximum modifier multiplied to the current price based on the provided state value\n            ',
            tunable_type=float,
            default_lower=1,
            default_upper=1))
    PHOTO_VALUE_SKILL_CURVE = TunableStatisticModifierCurve.TunableFactory(
        description=
        "\n        Allows you to adjust the final value of the photo based on the Sim's\n        level of a given skill.\n        ",
        axis_name_overrides=('Skill Level', 'Simoleon Multiplier'),
        locked_args={'subject': ParticipantType.Actor})
    PHOTOGRAPHY_SKILL = Skill.TunablePackSafeReference(
        description='\n        A reference to the photography skill.\n        '
    )
    EMOTION_STATE_MAP = TunableMapping(
        description=
        "\n        The mapping of moods to states, used to give photo objects a mood\n        based state. These states are then used by the tooltip component to\n        display emotional content on the photo's tooltip.\n        ",
        key_type=TunableReference(
            description=
            '\n            The mood to associate with a state.\n            ',
            manager=services.mood_manager()),
        value_type=TunableStateValueReference(
            description=
            '\n            The state that represents the mood for the purpose of displaying\n            emotional content in a tooltip.\n            '
        ))
    PHOTO_OBJECT_LOOT_PER_TARGET = TunableList(
        description=
        '\n        A list of loots which will be applied once PER target. The participants\n        for each application will be Actor: photographer, Target: photograph\n        target and Object: the Photograph itself. If a photo interaction has 2\n        target sims, this loot will be applied twice.\n        ',
        tunable=TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.ACTION),
                                 pack_safe=True))
    MOOD_PARAM_TO_MOOD_CATEGORY_STATE = TunableMapping(
        description=
        '\n        If the player took a picture in a photo mode that supports mood\n        categories, we will perform a state change to the corresponding state\n        based on the mood that each picture was taken in.\n        ',
        key_type=Tunable(
            description=
            '\n            The mood ASM parameter value to associate with a state.\n            ',
            tunable_type=str,
            default=None),
        value_type=TunableStateValueReference(
            description=
            '\n            The state that represents the mood category.\n            '
        ))
    GROUP_PHOTO_X_ACTOR_TAG = TunableEnumEntry(
        description=
        '\n        Tag to specify the photo studio interaction that the photo target sim\n        who should be considered the x actor will run.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID,
        invalid_enums=(tag.Tag.INVALID, ))
    GROUP_PHOTO_Y_ACTOR_TAG = TunableEnumEntry(
        description=
        '\n        Tag to specify the photo studio interaction that the photo target sim\n        who should be considered the y actor will run.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID,
        invalid_enums=(tag.Tag.INVALID, ))
    GROUP_PHOTO_Z_ACTOR_TAG = TunableEnumEntry(
        description=
        '\n        Tag to specify the photo studio interaction that the photo target sim\n        who should be considered the z actor will run.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID,
        invalid_enums=(tag.Tag.INVALID, ))
    NUM_PHOTOS_PER_SESSION = Tunable(
        description=
        '\n        Max possible photos that can be taken during one photo session. Once\n        this number has been reached, the photo session will exit.\n        ',
        tunable_type=int,
        default=5)

    @classmethod
    def _is_fail_photo(cls, photo_style_type):
        if photo_style_type == PhotoStyleType.EFFECT_GRAINY or (
                photo_style_type == PhotoStyleType.EFFECT_OVERSATURATED or
            (photo_style_type == PhotoStyleType.EFFECT_UNDERSATURATED or
             (photo_style_type == PhotoStyleType.PHOTO_FAIL_BLURRY or
              (photo_style_type == PhotoStyleType.PHOTO_FAIL_FINGER
               or photo_style_type == PhotoStyleType.PHOTO_FAIL_GNOME)))
        ) or photo_style_type == PhotoStyleType.PHOTO_FAIL_NOISE:
            return True
        return False

    @classmethod
    def _apply_quality_and_value_to_photo(cls, photographer_sim, photo_obj,
                                          photo_style, camera_quality):
        quality_stat = CraftingTuning.QUALITY_STATISTIC
        quality_stat_tracker = photo_obj.get_tracker(quality_stat)
        if cls._is_fail_photo(photo_style):
            final_quality = cls.FAIL_PHOTO_QUALITY_RANGE.random_int()
        else:
            quality_range = cls.BASE_PHOTO_QUALITY_MAP.get(
                camera_quality, None)
            if quality_range is None:
                logger.error(
                    'Photography tuning BASE_PHOTO_QUALITY_MAP does not have an expected quality value: []',
                    str(camera_quality))
                return
            base_quality = quality_range.random_int()
            skill_quality_modifier = 0
            if cls.PHOTOGRAPHY_SKILL is not None:
                effective_skill_level = photographer_sim.get_effective_skill_level(
                    cls.PHOTOGRAPHY_SKILL)
                if effective_skill_level:
                    skill_quality_modifier = effective_skill_level * cls.QUALITY_MODIFIER_PER_SKILL_LEVEL
            final_quality = base_quality + skill_quality_modifier
        quality_stat_tracker.set_value(quality_stat, final_quality)
        value_multiplier = 1
        for (state_value, value_mods) in cls.PHOTO_VALUE_MODIFIER_MAP.items():
            if photo_obj.has_state(state_value.state):
                actual_state_value = photo_obj.get_state(state_value.state)
                if state_value is actual_state_value:
                    value_multiplier *= value_mods.random_float()
                    break
        value_multiplier *= cls.PHOTO_VALUE_SKILL_CURVE.get_multiplier(
            SingleSimResolver(photographer_sim), photographer_sim)
        photo_obj.base_value = int(photo_obj.base_value * value_multiplier)

    @classmethod
    def _get_mood_sim_info_if_exists(cls, photographer_sim_info,
                                     target_sim_ids, camera_mode):
        if camera_mode is CameraMode.SELFIE_PHOTO:
            return photographer_sim_info
        else:
            num_target_sims = len(target_sim_ids)
            if num_target_sims == 1:
                sim_info_manager = services.sim_info_manager()
                target_sim_info = sim_info_manager.get(target_sim_ids[0])
                return target_sim_info

    @classmethod
    def _apply_mood_state_if_appropriate(cls, photographer_sim_info,
                                         target_sim_ids, camera_mode,
                                         photo_object):
        mood_sim_info = cls._get_mood_sim_info_if_exists(
            photographer_sim_info, target_sim_ids, camera_mode)
        if mood_sim_info:
            mood = mood_sim_info.get_mood()
            mood_state = cls.EMOTION_STATE_MAP.get(mood, None)
            if mood_state:
                photo_object.set_state(mood_state.state, mood_state)

    @classmethod
    def _apply_mood_category_state_if_appropriate(cls, selected_mood_param,
                                                  camera_mode, photo_object):
        if camera_mode in (CameraMode.TRIPOD, CameraMode.SIM_PHOTO,
                           CameraMode.PHOTO_STUDIO_PHOTO):
            mood_category_state = cls.MOOD_PARAM_TO_MOOD_CATEGORY_STATE.get(
                selected_mood_param, None)
            if mood_category_state:
                photo_object.set_state(mood_category_state.state,
                                       mood_category_state)

    @classmethod
    def create_photo_from_photo_data(cls, camera_mode, camera_quality,
                                     photographer_sim_id, target_obj_id,
                                     target_sim_ids, res_key, photo_style,
                                     photo_size, photo_orientation,
                                     photographer_sim_info, photographer_sim,
                                     time_stamp, selected_mood_param):
        photo_object = None
        is_paint_by_reference = camera_mode is CameraMode.PAINT_BY_REFERENCE
        if is_paint_by_reference:
            current_zone = services.current_zone()
            photo_object = current_zone.object_manager.get(target_obj_id)
            if photo_object is None:
                photo_object = current_zone.inventory_manager.get(
                    target_obj_id)
        else:
            if photo_orientation == PhotoOrientation.LANDSCAPE:
                if photo_size == PhotoSize.LARGE:
                    photo_object_def = cls.LARGE_LANDSCAPE_OBJ_DEF
                elif photo_size == PhotoSize.MEDIUM:
                    photo_object_def = cls.MEDIUM_LANDSCAPE_OBJ_DEF
                elif photo_size == PhotoSize.SMALL:
                    photo_object_def = cls.SMALL_LANDSCAPE_OBJ_DEF
            elif photo_orientation == PhotoOrientation.PORTRAIT:
                if photo_size == PhotoSize.LARGE:
                    photo_object_def = cls.LARGE_PORTRAIT_OBJ_DEF
                elif photo_size == PhotoSize.MEDIUM:
                    photo_object_def = cls.MEDIUM_PORTRAIT_OBJ_DEF
                elif photo_size == PhotoSize.SMALL:
                    photo_object_def = cls.SMALL_PORTRAIT_OBJ_DEF
                else:
                    photo_object_def = cls.SMALL_LANDSCAPE_OBJ_DEF
            if photo_object_def is None:
                return
            photo_object = create_object(photo_object_def)
        if photo_object is None:
            logger.error('photo object could not be found.')
            return
        for target_sim_id in target_sim_ids:
            target_sim_info = services.sim_info_manager().get(target_sim_id)
            target_sim = target_sim_info.get_sim_instance()
            resolver = DoubleSimAndObjectResolver(photographer_sim,
                                                  target_sim,
                                                  photo_object,
                                                  source=cls)
            for loot in cls.PHOTO_OBJECT_LOOT_PER_TARGET:
                loot.apply_to_resolver(resolver)
        photography_service = services.get_photography_service()
        loots = photography_service.get_loots_for_photo()
        for photoloot in loots:
            if photoloot._AUTO_FACTORY.FACTORY_TYPE is RotateTargetPhotoLoot:
                photographer_sim = photoloot.photographer
                photographer_sim_info = photographer_sim.sim_info
                break
        reveal_level = PaintingState.REVEAL_LEVEL_MIN if is_paint_by_reference else PaintingState.REVEAL_LEVEL_MAX
        painting_state = PaintingState.from_key(res_key, reveal_level, False,
                                                photo_style)
        photo_object.canvas_component.painting_state = painting_state
        photo_object.canvas_component.time_stamp = time_stamp
        photo_object.set_household_owner_id(photographer_sim.household_id)
        if selected_mood_param:
            cls._apply_mood_category_state_if_appropriate(
                selected_mood_param, camera_mode, photo_object)
        if not is_paint_by_reference:
            cls._apply_quality_and_value_to_photo(photographer_sim,
                                                  photo_object, photo_style,
                                                  camera_quality)
            cls._apply_mood_state_if_appropriate(photographer_sim_info,
                                                 target_sim_ids, camera_mode,
                                                 photo_object)
            photo_object.add_dynamic_component(STORED_SIM_INFO_COMPONENT,
                                               sim_id=photographer_sim.id)
            photo_object.update_object_tooltip()
            if not (photographer_sim.inventory_component.can_add(photo_object)
                    and photographer_sim.inventory_component.
                    player_try_add_object(photo_object)):
                logger.error(
                    "photo object could not be put in the sim's inventory, deleting photo."
                )
                photo_object.destroy()
        photo_targets = [
            services.sim_info_manager().get(sim_id)
            for sim_id in target_sim_ids
        ]
        if camera_mode == CameraMode.TWO_SIM_SELFIE_PHOTO:
            photo_targets.append(photographer_sim_info)
        photo_targets = frozenset(photo_targets)
        services.get_event_manager().process_event(
            test_events.TestEvent.PhotoTaken,
            sim_info=photographer_sim_info,
            photo_object=photo_object,
            photo_targets=photo_targets)
class ObjectTeleportationComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=types.OBJECT_TELEPORTATION_COMPONENT):
    ON_CLIENT_CONNECT = 0
    FACTORY_TUNABLES = {'when_to_teleport': TunableVariant(description='\n            When this object should teleport around.\n            ', locked_args={'on_client_connect': ON_CLIENT_CONNECT}, default='on_client_connect'), 'chance_to_teleport': TunablePercent(description='\n            A percent chance that this object will teleport when the\n            appropriate situation arises.\n            ', default=100), 'required_states': OptionalTunable(TunableList(description='\n            The states this object is required to be in in order to teleport.\n            ', tunable=TunableStateValueReference())), 'objects_to_teleport_near': TunableList(description='\n            A tunable list of static commodities, weights and behavior.  When\n            choosing where to teleport, objects with higher weights have a\n            greater chance of being chosen.\n            \n            If we fail to find a valid location near an object advertising the\n            chosen static commodity, we will search try again with a new object\n            until the list has been exhausted.\n            ', tunable=TunableTuple(description='\n                A static commodity and weight.\n                ', weight=TunableRange(description='\n                    A weight, between 0 and 1, that determines how likely this\n                    static commodity is to be chosen over the others listed.\n                    ', tunable_type=float, minimum=0, maximum=1, default=1), static_commodity=TunableReference(description='\n                    Reference to a type of static commodity.\n                    ', manager=services.static_commodity_manager()), state_change=OptionalTunable(TunableStateValueReference(description='\n                    A state value to apply to the object advertising this\n                    commodity if the teleport succeeds.\n                    '))))}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        zone = services.current_zone()
        if self.when_to_teleport == self.ON_CLIENT_CONNECT and not zone.is_zone_running:
            zone.register_callback(zone_types.ZoneState.CLIENT_CONNECTED, self.teleport)

    def teleport(self):
        if random.random() > self.chance_to_teleport:
            return
        if self.required_states is not None:
            for state in self.required_states:
                if not self.owner.state_value_active(state):
                    return
        weights_and_commodities = [(obj_dict.weight, obj_dict.static_commodity, obj_dict.state_change) for obj_dict in self.objects_to_teleport_near]
        while weights_and_commodities:
            index = sims4.random._weighted(weights_and_commodities)
            (_, static_commodity, state_change) = weights_and_commodities.pop(index)
            motives = set()
            motives.add(static_commodity)
            all_objects = list(services.object_manager().valid_objects())
            random.shuffle(all_objects)
            for obj in all_objects:
                if obj is self.owner:
                    continue
                if obj.commodity_flags & motives:
                    starting_location = placement.create_starting_location(position=obj.position)
                    if self.owner.is_sim:
                        fgl_context = placement.create_fgl_context_for_sim(starting_location, self.owner)
                    else:
                        fgl_context = placement.create_fgl_context_for_object(starting_location, self.owner)
                    (position, orientation) = placement.find_good_location(fgl_context)
                    if position is not None and orientation is not None:
                        self.owner.transform = sims4.math.Transform(position, orientation)
                        if state_change is not None:
                            obj.set_state(state_change.state, state_change)
                        break
Example #11
0
class Baby(GameObject):
    __qualname__ = 'Baby'
    MAX_PLACEMENT_ATTEMPTS = 8
    BABY_BASSINET_DEFINITION_MAP = TunableMapping(
        description=
        '\n        The corresponding mapping for each definition pair of empty bassinet\n        and bassinet with baby inside. The reason we need to have two of\n        definitions is one is deletable and the other one is not.\n        ',
        key_name='Baby',
        key_type=TunableReference(
            description=
            '\n            Bassinet with Baby object definition id.\n            ',
            manager=services.definition_manager()),
        value_name='EmptyBassinet',
        value_type=TunableReference(
            description=
            '\n            Bassinet with Baby object definition id.\n            ',
            manager=services.definition_manager()))
    BASSINET_EMPTY_STATE = TunableStateValueReference(
        description='\n        The state value for an empty bassinet.\n        '
    )
    BASSINET_BABY_STATE = TunableStateValueReference(
        description=
        '\n        The state value for a non-empty bassinet.\n        ')
    STATUS_STATE = ObjectState.TunableReference(
        description=
        '\n        The state defining the overall status of the baby (e.g. happy, crying,\n        sleeping). We use this because we need to reapply this state to restart\n        animations after a load.\n        '
    )
    BABY_SKIN_TONE_STATE_MAPPING = TunableMapping(
        description=
        '\n        From baby skin tone enum to skin tone state mapping.\n        ',
        key_type=TunableEnumEntry(tunable_type=BabySkinTone,
                                  default=BabySkinTone.MEDIUM),
        value_type=TunableTuple(boy=TunableStateValueReference(),
                                girl=TunableStateValueReference()))
    BABY_MOOD_MAPPING = TunableMapping(
        description=
        '\n        From baby state (happy, crying, sleep) to in game mood.\n        ',
        key_type=TunableStateValueReference(),
        value_type=Mood.TunableReference())
    BABY_SKIN_TONE_TO_CAS_SKIN_TONE = TunableMapping(
        description=
        '\n        From baby skin tone enum to cas skin tone id mapping.\n        ',
        key_type=TunableEnumEntry(tunable_type=BabySkinTone,
                                  default=BabySkinTone.MEDIUM),
        value_type=TunableList(
            description=
            '\n            The Skin Tones CAS reference under Catalog/InGame/CAS/Skintones.\n            ',
            tunable=TunableSkinTone()),
        export_modes=ExportModes.All,
        tuple_name='BabySkinToneToCasTuple')
    SEND_BABY_TO_DAYCARE_NOTIFICATION_SINGLE_BABY = TunableUiDialogNotificationSnippet(
        description=
        '\n        The message appearing when a single baby is sent to daycare. You can\n        reference this single baby by name.\n        '
    )
    SEND_BABY_TO_DAYCARE_NOTIFICATION_MULTIPLE_BABIES = TunableUiDialogNotificationSnippet(
        description=
        '\n        The message appearing when multiple babies are sent to daycare. You can\n        not reference any of these babies by name.\n        '
    )
    BRING_BABY_BACK_FROM_DAYCARE_NOTIFICATION_SINGLE_BABY = TunableUiDialogNotificationSnippet(
        description=
        '\n        The message appearing when a single baby is brought back from daycare.\n        You can reference this single baby by name.\n        '
    )
    BRING_BABY_BACK_FROM_DAYCARE_NOTIFICATION_MULTIPLE_BABIES = TunableUiDialogNotificationSnippet(
        description=
        '\n        The message appearing when multiple babies are brought back from\n        daycare. You can not reference any of these babies by name.\n        '
    )
    BABY_AGE_UP = TunableTuple(
        description=
        '\n        Multiple settings for baby age up moment.\n        ',
        age_up_affordance=TunableReference(
            description=
            '\n            The affordance to run when baby age up to kid.\n            ',
            manager=services.affordance_manager(),
            class_restrictions='SuperInteraction'),
        copy_states=TunableList(
            description=
            '\n            The list of the state we want to copy from the original baby\n            bassinet to the new bassinet to play idle.\n            ',
            tunable=TunableReference(manager=services.object_state_manager(),
                                     class_restrictions='ObjectState')),
        idle_state_value=TunableReference(
            description=
            '\n            The state value to apply on the new baby bassinet with the age up\n            special idle animation/vfx linked.\n            ',
            manager=services.object_state_manager(),
            class_restrictions='ObjectStateValue'))
    BABY_PLACEMENT_TAGS = TunableList(
        TunableEnumEntry(
            tag.Tag,
            tag.Tag.INVALID,
            description=
            '\n            Attempt to place the baby near objects with this tag set.\n            '
        ),
        description=
        '\n        When trying to place a baby bassinet on the lot, we attempt to place it\n        near other objects on the lot. Those objects are determined in priority\n        order by this tuned list. It will try to place next to all objects of the\n        matching types, before trying to place the baby in the middle of the lot,\n        and then finally trying the mailbox. If all FGL placements fail, we put\n        the baby into the household inventory.\n        '
    )
    BABY_THUMBNAIL_DEFINITION = TunableReference(
        description=
        '\n        The thumbnail definition for client use only.\n        ',
        manager=services.definition_manager(),
        export_modes=(ExportModes.ClientBinary, ))
    NEGLECTED_STATES = TunableList(
        description=
        '\n        If the baby enters any of these states, the neglected moment will begin.\n        ',
        tunable=TunableStateValueReference(
            description=
            '\n            The state to listen for in order to push the neglected moment on the baby.\n            '
        ))
    NEGLECT_ANIMATION = TunableReference(
        description=
        '\n        The animation to play on the baby for the neglect moment.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.ANIMATION),
        class_restrictions='ObjectAnimationElement')
    NEGLECT_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        The notification to show when the baby neglect moment happens.\n        '
    )
    NEGLECT_EFFECT = Tunable(
        description=
        '\n        The VFX to play during the neglect moment.\n        ',
        tunable_type=str,
        default='s40_Sims_neglected')
    NEGLECT_EMPTY_BASSINET_STATE = TunableStateValueReference(
        description=
        '\n        The state that will be set on the bassinet when it is emptied due to\n        neglect. This should control any reaction broadcasters that we want to\n        happen when the baby is taken away. This MUST be tuned.\n        '
    )
    NEGLECT_BUFF_IMMEDIATE_FAMILY = TunableBuffReference(
        description=
        "\n        The buff to be applied to the baby's immediate family during the \n        neglect moment.\n        "
    )
    FAILED_PLACEMENT_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        The notification to show if a baby could not be spawned into the world\n        because FGL failed. This is usually due to the player cluttering their\n        lot with objects. Token 0 is the baby.\n        '
    )

    @classmethod
    def get_baby_skin_tone_enum(cls, sim_info):
        if sim_info.is_baby:
            skin_tone_id = sim_info.skin_tone
            for (skin_enum,
                 tone_ids) in cls.BABY_SKIN_TONE_TO_CAS_SKIN_TONE.items():
                while skin_tone_id in tone_ids:
                    return skin_enum
            logger.error(
                'Baby with skin tone id {} not in BABY_SKIN_TONE_TO_CAS_SKIN_TONE. Setting light skin tone instead.',
                skin_tone_id,
                owner='jjacobson')
            return BabySkinTone.LIGHT
        return BabySkinTone.ADULT_SIM

    @classmethod
    def get_baby_skin_tone_state(cls, sim_info):
        skin_tone_state_value = None
        baby_skin_enum = cls.get_baby_skin_tone_enum(sim_info)
        if baby_skin_enum is not None and baby_skin_enum in cls.BABY_SKIN_TONE_STATE_MAPPING:
            skin_state_tuple = cls.BABY_SKIN_TONE_STATE_MAPPING[baby_skin_enum]
            if sim_info.gender == Gender.FEMALE:
                skin_tone_state_value = skin_state_tuple.girl
            elif sim_info.gender == Gender.MALE:
                skin_tone_state_value = skin_state_tuple.boy
        return skin_tone_state_value

    @classmethod
    def get_corresponding_definition(cls, definition):
        if definition in cls.BABY_BASSINET_DEFINITION_MAP:
            return cls.BABY_BASSINET_DEFINITION_MAP[definition]
        for (baby_def,
             bassinet_def) in cls.BABY_BASSINET_DEFINITION_MAP.items():
            while bassinet_def is definition:
                return baby_def

    @classmethod
    def get_default_baby_def(cls):
        return next(iter(cls.BABY_BASSINET_DEFINITION_MAP), None)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sim_info = None
        self.state_component.state_trigger_enabled = False
        self._started_neglect_moment = False
        self._ignore_daycare = False

    def set_sim_info(self, sim_info, ignore_daycare=False):
        self._sim_info = sim_info
        self._ignore_daycare = ignore_daycare
        if self._sim_info is not None:
            self.state_component.state_trigger_enabled = True
            self.enable_baby_state()

    @property
    def sim_info(self):
        return self._sim_info

    @property
    def is_selectable(self):
        return self.sim_info.is_selectable

    @property
    def sim_id(self):
        if self._sim_info is not None:
            return self._sim_info.sim_id
        return self.id

    @property
    def household_id(self):
        if self._sim_info is not None:
            return self._sim_info.household.id
        return 0

    def populate_localization_token(self, *args, **kwargs):
        if self.sim_info is not None:
            return self.sim_info.populate_localization_token(*args, **kwargs)
        logger.warn(
            'self.sim_info is None in baby.populate_localization_token',
            owner='epanero',
            trigger_breakpoint=True)
        return super().populate_localization_token(*args, **kwargs)

    def enable_baby_state(self):
        if self._sim_info is None:
            return
        self.set_state(self.BASSINET_BABY_STATE.state,
                       self.BASSINET_BABY_STATE)
        status_state = self.get_state(self.STATUS_STATE)
        self.set_state(status_state.state, status_state, force_update=True)
        skin_tone_state = self.get_baby_skin_tone_state(self._sim_info)
        if skin_tone_state is not None:
            self.set_state(skin_tone_state.state, skin_tone_state)

    def empty_baby_state(self):
        self.set_state(self.BASSINET_EMPTY_STATE.state,
                       self.BASSINET_EMPTY_STATE)

    def on_state_changed(self, state, old_value, new_value):
        super().on_state_changed(state, old_value, new_value)
        if new_value in self.NEGLECTED_STATES and not self._started_neglect_moment:
            start_baby_neglect(self)
        elif self.manager is not None and new_value in self.BABY_MOOD_MAPPING:
            mood = self.BABY_MOOD_MAPPING[new_value]
            mood_msg = Commodities_pb2.MoodUpdate()
            mood_msg.sim_id = self.id
            mood_msg.mood_key = mood.guid64
            mood_msg.mood_intensity = 1
            distributor.shared_messages.add_object_message(
                self, MSG_SIM_MOOD_UPDATE, mood_msg, False)

    def load_object(self, object_data):
        super().load_object(object_data)
        self._sim_info = services.sim_info_manager().get(self.sim_id)

    def on_finalize_load(self):
        sim_info = services.sim_info_manager().get(self.sim_id)
        if sim_info is None or sim_info.household is not services.active_lot(
        ).get_household():
            _replace_bassinet(sim_info, bassinet=self)
        else:
            self.set_sim_info(sim_info)
Example #12
0
class ObjectCreationElement(XevtTriggeredElement):
    __qualname__ = 'ObjectCreationElement'
    POSITION = 'position'
    INVENTORY = 'inventory'
    SLOT = 'slot'

    class ObjectDefinition(HasTunableSingletonFactory, AutoFactoryInit):
        __qualname__ = 'ObjectCreationElement.ObjectDefinition'
        FACTORY_TUNABLES = {
            'definition':
            TunableReference(
                description=
                '\n                The definition of the object that is created.\n                ',
                manager=services.definition_manager())
        }

        def get_definition(self):
            return self.definition

        def setup_created_object(self, interaction, created_object):
            pass

    class RecipeDefinition(HasTunableSingletonFactory, AutoFactoryInit):
        __qualname__ = 'ObjectCreationElement.RecipeDefinition'
        FACTORY_TUNABLES = {
            'recipe':
            TunableReference(
                description=
                '\n                The recipe to use to create the object.\n                ',
                manager=services.recipe_manager())
        }

        def get_definition(self):
            return self.recipe.final_product.definition

        def setup_created_object(self, interaction, created_object):
            from crafting.crafting_process import CraftingProcess
            crafting_process = CraftingProcess(crafter=interaction.sim,
                                               recipe=self.recipe)
            crafting_process.setup_crafted_object(created_object,
                                                  is_final_product=True)

    FACTORY_TUNABLES = {
        'description':
        '\n            Create an object as part of an interaction.\n            ',
        'creation_data':
        TunableVariant(
            description='\n            Define what to create.\n            ',
            definition=ObjectDefinition.TunableFactory(),
            recipe=RecipeDefinition.TunableFactory(),
            default='definition'),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the object as soon as it is created.\n            ',
            tunable=TunableStateValueReference()),
        'destroy_on_placement_failure':
        Tunable(
            description=
            '\n            If checked, the created object will be destroyed on placement failure.\n            If unchecked, the created object will be placed into an appropriate\n            inventory on placement failure if possible.  If THAT fails, object\n            will be destroyed.\n            ',
            tunable_type=bool,
            default=False),
        'cancel_on_destroy':
        Tunable(
            description=
            '\n            If checked, the interaction will be canceled if object is destroyed\n            due to placement failure or if destroy on placement failure is\n            unchecked and the fallback fails.\n            ',
            tunable_type=bool,
            default=True),
        'transient':
        Tunable(
            description=
            '\n            If checked, the created object will be destroyed when the interaction ends.\n            ',
            tunable_type=bool,
            default=False),
        'location':
        TunableVariant(
            description=
            '\n            Where the object should be created.\n            ',
            default='position',
            position=TunableTuple(
                description=
                '\n                An in-world position based off of the chosen Participant Type.\n                ',
                locked_args={'location': POSITION},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    Who or what to create this object next to.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor),
                offset_tuning=TunableTuple(
                    default_offset=TunableVector3(
                        description=
                        "\n                        The default Vector3 offset from the location target's\n                        position.\n                        ",
                        default=sims4.math.Vector3.ZERO()),
                    x_randomization_range=OptionalTunable(
                        TunableInterval(
                            description=
                            '\n                        A random number in this range will be applied to the\n                        default offset along the x axis.\n                        ',
                            tunable_type=float,
                            default_lower=0,
                            default_upper=0)),
                    z_randomization_range=OptionalTunable(
                        TunableInterval(
                            description=
                            '\n                        A random number in this range will be applied to the\n                        default offset along the z axis.\n                        ',
                            tunable_type=float,
                            default_lower=0,
                            default_upper=0))),
                ignore_bb_footprints=Tunable(
                    description=
                    '\n                    Ignores the build buy object footprints when trying to find\n                    a position for creating this object.  This will allow \n                    objects to appear on top of each other.\n                    e.g. Trash cans when tipped over want to place the trash \n                    right under them so it looks like the pile came out from \n                    the object while it was tipped.\n                    ',
                    tunable_type=bool,
                    default=True),
                allow_off_lot_placement=Tunable(
                    description=
                    '\n                    If checked, objects will be allowed to be placed off-lot.\n                    If unchecked, we will always attempt to place created\n                    objects on the active lot.\n                    ',
                    tunable_type=bool,
                    default=False)),
            inventory=TunableTuple(
                description=
                '\n                An inventory based off of the chosen Participant Type.\n                ',
                locked_args={'location': INVENTORY},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    "The owner of the inventory the object will be created in."\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor)),
            slot=TunableTuple(
                description=
                '\n                Slot the object into the specified slot on the tuned location_target.\n                ',
                locked_args={'location': SLOT},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    The object which will contain the specified slot.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Object),
                parent_slot=TunableVariant(
                    description=
                    '\n                    The slot on location_target where the object should go. This\n                    may be either the exact name of a bone on the location_target or a\n                    slot type, in which case the first empty slot of the specified type\n                    in which the child object fits will be used.\n                    ',
                    by_name=Tunable(
                        description=
                        '\n                        The exact name of a slot on the location_target in which the target\n                        object should go.  \n                        ',
                        tunable_type=str,
                        default='_ctnm_'),
                    by_reference=TunableReference(
                        description=
                        '\n                        A particular slot type in which the target object should go.  The\n                        first empty slot of this type found on the location_target will be used.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SLOT_TYPE))))),
        'reserve_object':
        OptionalTunable(
            description=
            '\n            If this is enabled, the created object will be reserved for use by\n            the set Sim.\n            ',
            tunable=TunableEnumEntry(
                tunable_type=ParticipantTypeActorTargetSim,
                default=ParticipantTypeActorTargetSim.Actor))
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(interaction, sequence=sequence, *args, **kwargs)
        self._placement_failed = False
        if self.reserve_object is not None:
            reserved_sim = self.interaction.get_participant(
                self.reserve_object)
        else:
            reserved_sim = None
        self._object_helper = CreateObjectHelper(
            reserved_sim,
            self.definition,
            self,
            init=self._setup_created_object)

    @property
    def definition(self):
        return self.creation_data.get_definition()

    @property
    def placement_failed(self):
        return self._placement_failed

    def create_object(self):
        created_object = create_object(self.definition,
                                       init=self._setup_created_object,
                                       post_add=self._place_object)
        if self._placement_failed:
            created_object.destroy(
                source=self.interaction,
                cause='Failed to place object created by basic extra.')
            return
        return created_object

    def _build_outer_elements(self, sequence):
        return self._object_helper.create(sequence)

    def _do_behavior(self):
        self._place_object(self._object_helper.object)
        if self._placement_failed:
            if self.cancel_on_destroy:
                self.interaction.cancel(
                    FinishingType.FAILED_TESTS,
                    cancel_reason_msg='Cannot place object')
                return False
            return True
        if not self.transient:
            self._object_helper.claim()
        return True

    def _setup_created_object(self, created_object):
        self.creation_data.setup_created_object(self.interaction,
                                                created_object)
        for initial_state in self.initial_states:
            created_object.set_state(initial_state.state, initial_state)

    def _get_ignored_object_ids(self):
        pass

    def _place_object_no_fallback(self, created_object):
        participant = self.interaction.get_participant(
            self.location.location_target)
        if self.location.location == self.POSITION:
            offset_tuning = self.location.offset_tuning
            default_offset = sims4.math.Vector3(offset_tuning.default_offset.x,
                                                offset_tuning.default_offset.y,
                                                offset_tuning.default_offset.z)
            x_range = offset_tuning.x_randomization_range
            z_range = offset_tuning.z_randomization_range
            start_orientation = sims4.random.random_orientation()
            if x_range is not None:
                x_axis = start_orientation.transform_vector(
                    sims4.math.Vector3.X_AXIS())
                default_offset += x_axis * random.uniform(
                    x_range.lower_bound, x_range.upper_bound)
            if z_range is not None:
                z_axis = start_orientation.transform_vector(
                    sims4.math.Vector3.Z_AXIS())
                default_offset += z_axis * random.uniform(
                    z_range.lower_bound, z_range.upper_bound)
            offset = sims4.math.Transform(default_offset,
                                          sims4.math.Quaternion.IDENTITY())
            start_position = sims4.math.Transform.concatenate(
                offset, participant.transform).translation
            routing_surface = participant.routing_surface
            active_lot = services.active_lot()
            search_flags = placement.FGLSearchFlagsDefault
            if self.location.allow_off_lot_placement and not active_lot.is_position_on_lot(
                    start_position):
                created_object.location = sims4.math.Location(
                    sims4.math.Transform(start_position, start_orientation),
                    routing_surface)
                polygon = placement.get_accurate_placement_footprint_polygon(
                    created_object.position, created_object.orientation,
                    created_object.scale, created_object.get_footprint())
                context = placement.FindGoodLocationContext(
                    starting_position=start_position,
                    starting_orientation=start_orientation,
                    starting_routing_surface=routing_surface,
                    ignored_object_ids=(created_object.id, ),
                    search_flags=search_flags,
                    object_polygons=(polygon, ))
            else:
                if not self.location.ignore_bb_footprints:
                    search_flags |= placement.FGLSearchFlag.SHOULD_TEST_BUILDBUY | placement.FGLSearchFlag.STAY_IN_CURRENT_BLOCK
                    if not active_lot.is_position_on_lot(start_position):
                        start_position = active_lot.get_default_position(
                            position=start_position)
                context = placement.FindGoodLocationContext(
                    starting_position=start_position,
                    starting_orientation=start_orientation,
                    starting_routing_surface=routing_surface,
                    object_id=created_object.id,
                    ignored_object_ids=self._get_ignored_object_ids(),
                    search_flags=search_flags,
                    object_footprints=(self.definition.get_footprint(0), ))
            (translation, orientation) = placement.find_good_location(context)
            if translation is not None:
                created_object.move_to(routing_surface=routing_surface,
                                       translation=translation,
                                       orientation=orientation)
                return True
        elif self.location.location == self.SLOT:
            parent_slot = self.location.parent_slot
            if participant.slot_object(parent_slot=parent_slot,
                                       slotting_object=created_object):
                return True
        return False

    def _place_object(self, created_object):
        if self._place_object_no_fallback(created_object):
            return True
        if not self.destroy_on_placement_failure:
            participant = self.interaction.get_participant(
                self.location.location_target)
            if participant.inventory_component is not None and created_object.inventoryitem_component is not None:
                if participant.is_sim:
                    participant_household_id = participant.household.id
                else:
                    participant_household_id = participant.get_household_owner_id(
                    )
                created_object.set_household_owner_id(participant_household_id)
                participant.inventory_component.system_add_object(
                    created_object, participant)
                return True
            sim = self.interaction.sim
            if sim is not None:
                if not sim.household.is_npc_household:
                    try:
                        created_object.set_household_owner_id(sim.household.id)
                        build_buy.move_object_to_household_inventory(
                            created_object)
                        return True
                    except KeyError:
                        pass
        self._placement_failed = True
        return False
Example #13
0
class RetailComponent(Component,
                      HasTunableFactory,
                      AutoFactoryInit,
                      component_name=types.RETAIL_COMPONENT,
                      persistence_priority=ComponentPriority.PRIORITY_RETAIL,
                      persistence_key=SimObjectAttributes_pb2.
                      PersistenceMaster.PersistableData.RetailComponent):
    FACTORY_TUNABLES = {
        'sellable':
        OptionalTunable(
            description=
            '\n            If enabled, this object can be sold on a retail lot.\n            ',
            disabled_name='Not_For_Sale',
            enabled_by_default=True,
            tunable=TunableTuple(
                description=
                '\n                The data associated with selling this item.\n                ',
                placard_override=OptionalTunable(
                    description=
                    '\n                    If enabled, we will only use this placard when this object\n                    is sold. If disabled, the game will attempt to smartly\n                    choose a placard.\n                    ',
                    tunable=TunableTuple(
                        description=
                        '\n                        The placard and vfx to use for this object.\n                        ',
                        model=TunablePackSafeReference(
                            description=
                            '\n                            The placard to use when this object is sold.\n                            ',
                            manager=services.definition_manager()),
                        vfx=PlayEffect.TunableFactory(
                            description=
                            '\n                            The effect to play when the object is sold.\n                            '
                        ))),
                for_sale_extra_affordances=TunableList(
                    description=
                    "\n                    When this object is marked For Sale, these are the extra\n                    interactions that will still be available. For instance, a\n                    debug interaction to sell the object could go here or you\n                    may want Sit to still be on chairs so customers can try\n                    them out. You may also want Clean on anything that can get\n                    dirty so employees can clean them.\n                    \n                    Note: These interactions are specific to this object. Do not\n                    add interactions that don't make sense for this object.\n                    ",
                    tunable=TunableReference(
                        description=
                        '\n                        The affordance to be left available on the object.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.INTERACTION),
                        pack_safe=True)),
                buy_affordance=TunablePackSafeReference(
                    description=
                    '\n                    The affordance a Sim will run to buy retail objects. This\n                    affordance should handle destroying the object and adding\n                    the money to the retail funds.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.INTERACTION)),
                restock_affordances=TunableList(
                    description=
                    '\n                    The affordances a Sim will run to restock retail objects.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            sims4.resources.Types.INTERACTION),
                        pack_safe=True)),
                clear_placard_affordance=TunablePackSafeReference(
                    description=
                    '\n                    The affordance a Sim will run to remove a placard from the lot.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.INTERACTION),
                    allow_none=True),
                browse_affordance=TunablePackSafeReference(
                    description=
                    '\n                    The affordance a Sim will run to browse retail objects.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.INTERACTION)),
                set_for_sale_affordance=TunablePackSafeReference(
                    description=
                    '\n                    The affordance a Sim will run to set a retail object as For\n                    Sale.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.INTERACTION),
                    allow_none=True),
                set_not_for_sale_affordance=TunablePackSafeReference(
                    description=
                    '\n                    The affordance a Sim will run to set a retail object as Not\n                    For Sale.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.INTERACTION),
                    allow_none=True),
                allowed_occupied_slots_for_sellable=TunableSet(
                    description=
                    '\n                    By default, an object will become unsellable if anything is\n                    parented to it. This is to prevent awkward scenarios such\n                    as selling a counter with a sink is parented, leaving the\n                    sink floating in mid-air. However, some slots on objects\n                    are okay occupied, e.g. a table should still be sellable if\n                    chairs are parented. Adding those slots to this set (e.g.\n                    slot_SitChair on tables) will allow this object to remain\n                    sellable even if those slots are occupied.\n                    ',
                    tunable=SlotType.TunableReference()))),
        'advertises':
        OptionalTunable(
            description=
            '\n            If enabled, this object will contribute to the curb appeal for the\n            retail lot on which it is placed. Curb appeal affects foot traffic\n            for the store as well as the type of clientele.\n            ',
            disabled_name='Does_Not_Advertise',
            enabled_by_default=True,
            tunable=RetailCurbAppeal.TunableFactory())
    }
    NOT_FOR_SALE_STATE = TunableStateValueReference(
        description=
        '\n        The state value that represents and object, on a retail lot, that is\n        not for sale at all. Objects in this state should function like normal.\n        ',
        pack_safe=True)
    FOR_SALE_STATE = TunableStateValueReference(
        description=
        '\n        The state value that represents an object that is valid for sale on a\n        retail lot. This is the state that will be tested in order to show sale\n        interactions.\n        ',
        pack_safe=True)
    SOLD_STATE = TunableStateValueReference(
        description=
        "\n        The state value that represents an object that is no longer valid for\n        sale on a retail lot. This is the state that will be set on the object\n        when it's sold and in its Placard form.\n        ",
        pack_safe=True)
    DEFAULT_SALE_STATE = TunableStateValueReference(
        description=
        '\n        This is the default for sale state for an object. When it is given a\n        retail component for this first time, this is what the For Sale state\n        will be set to.\n        ',
        pack_safe=True)
    SET_FOR_SALE_VFX = PlayEffect.TunableFactory(
        description=
        '\n        An effect that will play on an object when it gets set for sale.\n        '
    )
    SET_NOT_FOR_SALE_VFX = PlayEffect.TunableFactory(
        description=
        '\n        An effect that will play on an object when it gets set not for sale.\n        '
    )
    PLACARD_FLOOR = TunableTuple(
        description=
        '\n        The placard to use, and vfx to show, for objects that were on the floor\n        when sold.\n        ',
        model=TunableReference(
            description=
            '\n            The placard to use when the object is sold.\n            ',
            manager=services.definition_manager(),
            pack_safe=True),
        vfx=PlayEffect.TunableFactory(
            description=
            '\n            The effect to play when the object is sold.\n            '
        ))
    PLACARD_SURFACE = TunableTuple(
        description=
        '\n        The placard to use, and vfx to show, for objects that were on a surface\n        when sold.\n        ',
        model=TunableReference(
            description=
            '\n            The placard to use when the object is sold.\n            ',
            manager=services.definition_manager(),
            pack_safe=True),
        vfx=PlayEffect.TunableFactory(
            description=
            '\n            The effect to play when the object is sold.\n            '
        ))
    PLACARD_WALL = TunableTuple(
        description=
        '\n        The placard to use, and vfx to show, for objects that were on the wall\n        when sold.\n        ',
        model=TunableReference(
            description=
            '\n            The placard to use when the object is sold.\n            ',
            manager=services.definition_manager(),
            pack_safe=True),
        vfx=PlayEffect.TunableFactory(
            description=
            '\n            The effect to play when the object is sold.\n            '
        ))
    PLACARD_CEILING = TunableTuple(
        description=
        '\n        The placard to use, and vfx to show, for objects that were on the\n        ceiling when sold.\n        ',
        model=TunableReference(
            description=
            '\n            The placard to use when the object is sold.\n            ',
            manager=services.definition_manager(),
            pack_safe=True),
        vfx=PlayEffect.TunableFactory(
            description=
            '\n            The effect to play when the object is sold.\n            '
        ))
    UNINTERACTABLE_AUTONOMY_MODIFIER = TunableAutonomyModifier(
        description=
        '\n        Autonomy modifier that disables interactions on this object. Applied\n        to this object and its children when marked as sellable or sold.\n        ',
        locked_args={'relationship_multipliers': None})

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._cached_value = None
        self._uninteractable_autonomy_modifier_handle = None

    @classproperty
    def required_packs(cls):
        return (Pack.EP01, )

    def on_add(self):
        if self.owner.state_component is None:
            self.owner.add_component(
                StateComponent(self.owner,
                               states=(),
                               state_triggers=(),
                               unique_state_changes=None,
                               delinquency_state_changes=None,
                               timed_state_triggers=None))
        self.owner.set_state_dynamically(state=self.DEFAULT_SALE_STATE.state,
                                         new_value=self.DEFAULT_SALE_STATE,
                                         seed_value=self.SOLD_STATE)
        self.owner.update_component_commodity_flags()

    def save(self, persistence_master_message):
        persistable_data = SimObjectAttributes_pb2.PersistenceMaster.PersistableData(
        )
        persistable_data.type = SimObjectAttributes_pb2.PersistenceMaster.PersistableData.RetailComponent
        retail_data = persistable_data.Extensions[
            SimObjectAttributes_pb2.PersistableRetailComponent.
            persistable_data]
        if self._cached_value is not None:
            retail_data.cached_value = self._cached_value
        persistence_master_message.data.extend([persistable_data])

    def load(self, persistence_master_message):
        retail_data = persistence_master_message.Extensions[
            SimObjectAttributes_pb2.PersistableRetailComponent.
            persistable_data]
        if retail_data.HasField('cached_value'):
            self._cached_value = retail_data.cached_value

    def get_current_curb_appeal(self):
        curb_appeal = 0
        if self.advertises:
            curb_appeal = self.advertises.base_curb_appeal
            curb_appeal += sum([
                curb_appeal
                for (state, curb_appeal
                     ) in self.advertises.state_based_curb_appeal.items()
                if self.owner.state_value_active(state)
            ])
        return curb_appeal

    def on_state_changed(self, state, old_value, new_value, from_init):
        reapply_state = False
        if old_value is new_value:
            if state is not self.FOR_SALE_STATE.state:
                return
            reapply_state = True
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            return
        if new_value is self.NOT_FOR_SALE_STATE or new_value is self.DEFAULT_SALE_STATE:
            show_vfx = not reapply_state and (
                old_value is self.FOR_SALE_STATE
                or old_value is self.DEFAULT_SALE_STATE)
            if show_vfx:
                if self.owner.swapping_to_parent is not None:
                    if self.FOR_SALE_STATE in self.owner.swapping_to_parent.slot_component.state_values:
                        show_vfx = False
            self._set_not_for_sale_internal(show_vfx=show_vfx)
        elif new_value is self.FOR_SALE_STATE:
            show_vfx = not reapply_state and (
                old_value is self.NOT_FOR_SALE_STATE
                or old_value is self.DEFAULT_SALE_STATE)
            if show_vfx:
                if self.owner.swapping_to_parent is not None:
                    if self.NOT_FOR_SALE_STATE in self.owner.swapping_to_parent.slot_component.state_values:
                        show_vfx = False
            self._set_for_sale_internal(show_vfx=show_vfx)
            if old_value is self.SOLD_STATE:
                self.owner.remove_client_state_suppressor(state)
                self.owner.reset_states_to_default()
        elif new_value is self.SOLD_STATE:
            self.owner.add_client_state_suppressor(self.SOLD_STATE.state)
            self._set_sold_internal()
        self.owner.update_component_commodity_flags()

    def on_added_to_inventory(self):
        inventory = self.owner.get_inventory()
        if inventory is None:
            return
        if inventory.inventory_type in RetailUtils.RETAIL_INVENTORY_TYPES:
            if not self.is_sold:
                self.set_for_sale()
        else:
            if self.owner.get_state(
                    self.DEFAULT_SALE_STATE.state) == self.DEFAULT_SALE_STATE:
                return
            if not self.is_sold:
                self.set_not_for_sale()

    def on_child_added(self, child, location):
        if self.is_for_sale and not self.is_allowed_slot(location.slot_hash):
            self.set_not_for_sale()
        child_retail = child.retail_component
        if child_retail is not None and self.is_for_sale_or_sold:
            child_retail.set_uninteractable()

    def on_child_removed(self, child, new_parent=None):
        child_retail = child.retail_component
        if child_retail is not None:
            child_retail.set_interactable(from_unparent=True)

    def on_finalize_load(self):
        self.owner.update_component_commodity_flags()
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            if self.is_sold or self.is_for_sale:
                self.set_not_for_sale()
        elif self.is_sold:
            self.owner.add_client_state_suppressor(self.SOLD_STATE.state)

    @property
    def is_for_sale(self):
        return self.owner.state_value_active(self.FOR_SALE_STATE)

    @property
    def is_sold(self):
        return self.owner.state_value_active(self.SOLD_STATE)

    @property
    def is_not_for_sale(self):
        return self.owner.state_value_active(
            self.NOT_FOR_SALE_STATE) or self.owner.state_value_active(
                self.DEFAULT_SALE_STATE)

    @property
    def is_for_sale_or_sold(self):
        return not self.is_not_for_sale

    def get_retail_value(self):
        obj = self.owner
        crafting_component = obj.get_component(types.CRAFTING_COMPONENT)
        if crafting_component is None:
            return obj.catalog_value
        if self._cached_value is None:
            return obj.current_value
        state_component = obj.state_component
        if state_component is not None:
            return max(
                round(self._cached_value *
                      state_component.state_based_value_mod), 0)
        else:
            return self._cached_value

    def get_sell_price(self):
        base_value = self.get_retail_value()
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            logger.error(
                "Trying to get the sell price of a retail item but no retail_manager was found for this lot. Defaulting to the object's value without a markup applied."
            )
            return base_value
        return retail_manager.get_value_with_markup(base_value)

    def get_buy_affordance(self):
        sellable_data = self.sellable
        if sellable_data is not None:
            return sellable_data.buy_affordance

    def is_allowed_slot(self, slot_hash):
        if not self.sellable:
            return False
        for runtime_slot in self.owner.get_runtime_slots_gen(
                bone_name_hash=slot_hash):
            if self.sellable.allowed_occupied_slots_for_sellable & runtime_slot.slot_types:
                return True
        return False

    def get_can_be_sold(self):
        if not self.sellable:
            return False
        elif any(not self.is_allowed_slot(child.slot_hash)
                 for child in self.owner.children):
            return False
        return True

    def set_not_for_sale(self):
        self.owner.set_state(self.NOT_FOR_SALE_STATE.state,
                             self.NOT_FOR_SALE_STATE)

    def set_for_sale(self):
        self.owner.set_state(self.FOR_SALE_STATE.state, self.FOR_SALE_STATE)

    def _set_sold_internal(self):
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            logger.error(
                'Trying to set an item as sold but the retail manager is None.'
            )
            return
        item = self.owner
        self._handle_children_of_sold_object(item)
        self._change_to_placard()
        item.on_set_sold()

    def set_uninteractable(self, propagate_to_children=False):
        if self._uninteractable_autonomy_modifier_handle is not None:
            return
        self._uninteractable_autonomy_modifier_handle = self.owner.add_statistic_modifier(
            self.UNINTERACTABLE_AUTONOMY_MODIFIER)
        if propagate_to_children:
            for child in self.owner.children:
                retail_component = child.retail_component
                if retail_component is not None:
                    child.retail_component.set_uninteractable(
                        propagate_to_children=False)

    def set_interactable(self,
                         propagate_to_children=False,
                         from_unparent=False):
        if self._uninteractable_autonomy_modifier_handle is None:
            return
        if not from_unparent:
            parent = self.owner.parent
            if parent is not None:
                parent_retail = parent.retail_component
                if parent_retail is not None and parent_retail.is_for_sale_or_sold:
                    return
        self.owner.remove_statistic_modifier(
            self._uninteractable_autonomy_modifier_handle)
        self._uninteractable_autonomy_modifier_handle = None
        if propagate_to_children:
            for child in self.owner.children:
                retail_component = child.retail_component
                if retail_component is not None:
                    if not retail_component.is_for_sale_or_sold:
                        child.retail_component.set_interactable(
                            propagate_to_children=False)

    def _set_not_for_sale_internal(self, show_vfx=True):
        self.set_interactable(propagate_to_children=True)
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            return
        retail_manager.refresh_for_sale_vfx_for_object(self.owner)
        if show_vfx:
            self.SET_NOT_FOR_SALE_VFX(self.owner).start_one_shot()

    def _set_for_sale_internal(self, show_vfx=True):
        self.set_uninteractable(propagate_to_children=True)
        item = self.owner
        if item.standin_model is not None:
            item.standin_model = None
            item.on_restock()
        if self._cached_value is not None:
            item.base_value = self._cached_value
            self._cached_value = None
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            logger.error(
                "Trying to set an item For Sale but it's not on a valid retail lot."
            )
            return False
        retail_manager.refresh_for_sale_vfx_for_object(self.owner)
        if show_vfx:
            self.SET_FOR_SALE_VFX(self.owner).start_one_shot()
        return True

    def _choose_placard_info(self):
        placard_override = self.sellable.placard_override
        if placard_override is not None:
            if placard_override.model is not None:
                return placard_override
            logger.error(
                'Object [{}] has a placard override enabled on the retail component [{}] but the placard override has no model set. We will attempt to pick the correct placard.',
                self.owner, self)
        item = self.owner
        if item.parent is not None:
            return self.PLACARD_SURFACE
        if item.wall_or_fence_placement:
            return self.PLACARD_WALL
        if item.ceiling_placement:
            return self.PLACARD_CEILING
        else:
            return self.PLACARD_FLOOR

    def _change_to_placard(self, play_vfx=True):
        item = self.owner
        if self._cached_value is None:
            self._cached_value = item.base_value
        item.base_value = 0
        placard_info = self._choose_placard_info()
        item.standin_model = placard_info.model.definition.get_model(0)
        item.set_state(self.SOLD_STATE.state, self.SOLD_STATE)
        if play_vfx:
            effect = placard_info.vfx(item)
            effect.start_one_shot()
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            logger.error(
                "Trying to change a retail object to a placard but it's not on a valid retail lot."
            )
            return False
        retail_manager.refresh_for_sale_vfx_for_object(self.owner)

    def _handle_children_of_sold_object(self, obj):
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        active_household_is_owner = retail_manager.is_owner_household_active
        should_show_notification = False
        for child in tuple(obj.children):
            if child.has_component(types.RETAIL_COMPONENT):
                child.retail_component.set_not_for_sale()
            if active_household_is_owner:
                should_show_notification = True
                build_buy.move_object_to_household_inventory(child)
            else:
                child.schedule_destroy_asap()
        if should_show_notification:
            notification = retail_manager.ITEMS_SENT_TO_HH_INVENTORY_NOTIFICATION(
                obj)
            notification.show_dialog()

    def component_super_affordances_gen(self, **kwargs):
        retail_manager = services.business_service(
        ).get_retail_manager_for_zone()
        if retail_manager is None:
            return
        sellable_data = self.sellable
        if sellable_data is not None:
            if self.is_for_sale:
                yield from sellable_data.for_sale_extra_affordances
                if sellable_data.set_not_for_sale_affordance is not None:
                    yield sellable_data.set_not_for_sale_affordance
                    if retail_manager.is_open:
                        if sellable_data.buy_affordance is not None:
                            yield sellable_data.buy_affordance
                        if sellable_data.browse_affordance is not None:
                            yield sellable_data.browse_affordance
            elif self.is_not_for_sale:
                if sellable_data.set_for_sale_affordance is not None and self.get_can_be_sold(
                ):
                    yield sellable_data.set_for_sale_affordance
            elif self.is_sold:
                yield from sellable_data.restock_affordances
                if sellable_data.clear_placard_affordance is not None:
                    yield sellable_data.clear_placard_affordance

    def modify_interactable_flags(self, interactable_flag_field):
        if self.is_for_sale:
            if not self.is_sold:
                interactable_flag_field.flags |= interaction_protocol.Interactable.FORSALE
class InventoryTuning:
    INVALID_ACCESS_STATES = TunableList(
        TunableStateValueReference(
            description=
            '\n        If an inventory owner is in any of the states tuned here, it will not\n        be available to grab objects out of.\n        ',
            pack_safe=True))
class StateChangeLootOp(BaseLootOperation):
    __qualname__ = 'StateChangeLootOp'
    FACTORY_TUNABLES = {'description': '\n            This loot will change the state of the subject.\n            ', 'state_value': TunableStateValueReference()}

    def __init__(self, state_value, **kwargs):
        super().__init__(**kwargs)
        self.state_value = state_value

    def _apply_to_subject_and_target(self, subject, target, resolver):
        subject_obj = self._get_object_from_recipient(subject)
        if subject_obj is not None:
            state_value = self.state_value
            subject_obj.set_state(state_value.state, state_value)
class ModifyAllLotItems(HasTunableFactory, AutoFactoryInit):
    DESTROY_OBJECT = 0
    SET_STATE = 1
    INVENTORY_TRANSFER = 2
    DELIVER_BILLS = 3
    SET_ON_FIRE = 4
    CLEANUP_VEHICLE = 5
    LOOT = 6
    FACTORY_TUNABLES = {
        'description':
        '\n        Tune modifications to apply to all objects on a lot.\n        Can do state changes, destroy certain items, etc.\n        \n        EX: for auto cleaning, tune to have objects with Dirtiness state that\n        equals dirty to be set to the clean state and tune to have dirty dishes\n        and spoiled food to be deleted\n        ',
        'modifications':
        TunableList(
            description=
            "\n            A list of where the elements define how to modify objects on the\n            lot. Each entry is a triplet of an object modification action\n            (currently either destroy the object or set its state), a list of\n            tests to run on the object to determine if we should actually apply\n            the modification, and a priority in case some modifications should\n            take precedence over other ones when both of their tests pass.\n            \n            EX: test list: object's dirtiness state != dirtiness clean\n            action: set state to Dirtiness_clean\n            \n            So dirty objects will become clean\n            ",
            tunable=TunableTuple(
                action=TunableVariant(
                    set_state=TunableTuple(
                        action_value=TunableStateValueReference(
                            description='An object state to set the object to',
                            pack_safe=True),
                        locked_args={'action_type': SET_STATE}),
                    destroy_object=TunableTuple(
                        locked_args={'action_type': DESTROY_OBJECT}),
                    inventory_transfer=TunableTuple(
                        action_value=InventoryTransferFakePerform.
                        TunableFactory(),
                        locked_args={'action_type': INVENTORY_TRANSFER}),
                    deliver_bills=TunableTuple(
                        action_value=DeliverBillFakePerform.TunableFactory(),
                        locked_args={'action_type': DELIVER_BILLS}),
                    set_on_fire=TunableTuple(
                        locked_args={'action_type': SET_ON_FIRE}),
                    cleanup_vehicle=TunableTuple(
                        description=
                        '\n                        Cleanup vehicles that are left around.\n                        ',
                        locked_args={'action_type': CLEANUP_VEHICLE}),
                    loot=TunableTuple(
                        description=
                        '\n                        Apply loots to the object.\n                        ',
                        loot_actions=TunableSet(
                            description=
                            '\n                            Loot(s) to apply.\n                            ',
                            tunable=TunableReference(
                                manager=services
                                .get_instance_manager(
                                    Types.ACTION),
                                pack_safe
                                =True)),
                        locked_args={'action_type': LOOT})),
                chance=TunablePercent(
                    description=
                    '\n                    Chance this modification will occur.\n                    ',
                    default=100,
                    minimum=1),
                global_tests=TunableObjectModifyGlobalTestList(
                    description=
                    "\n                    Non object-related tests that gate this modification from occurring.  Use this for any global\n                    tests that don't require the object, such as zone/location/time-elapsed tests.  These tests\n                    will run only ONCE for this action, unlike 'Tests', which runs PER OBJECT. \n                    "
                ),
                tests=TunableObjectModifyTestSet(
                    description=
                    '\n                    All least one subtest group (AKA one list item) must pass\n                    within this list before the action associated with this\n                    tuning will be run.\n                    ',
                    additional_tests={
                        'elapsed_time':
                        TimeElapsedZoneTest.TunableFactory(
                            locked_args={'tooltip': None}),
                        'statistic':
                        StatThresholdTest.TunableFactory(
                            locked_args={'tooltip': None})
                    }),
                weighted_tests=TunableList(
                    description=
                    '\n                    Weighted tests for the individual object. One is chosen \n                    based on weight, and all objects are run against that chosen\n                    test set.\n                    ',
                    tunable=TunableTuple(
                        tests=TunableObjectModifyTestSet(
                            description=
                            '\n                            All least one subtest group (AKA one list item) must pass\n                            within this list before the action associated with this\n                            tuning will be run.\n                            ',
                            additional_tests={
                                'elapsed_time':
                                TimeElapsedZoneTest.TunableFactory(
                                    locked_args={'tooltip': None}),
                                'statistic':
                                StatThresholdTest.TunableFactory(
                                    locked_args={'tooltip': None})
                            }),
                        weight=TunableRange(
                            description=
                            '\n                            Weight to use.\n                            ',
                            tunable_type=int,
                            default=1,
                            minimum=1)))))
    }

    def modify_objects(self, object_criteria=None):
        objects_to_destroy = []
        num_modified = 0
        modifications = defaultdict(CompoundTestList)
        for mod in self.modifications:
            if not random_chance(mod.chance * 100):
                continue
            if mod.global_tests and not mod.global_tests.run_tests(
                    GlobalResolver()):
                continue
            if mod.tests:
                modifications[mod.action].extend(mod.tests)
            if mod.weighted_tests:
                weighted_tests = []
                for test_weight_pair in mod.weighted_tests:
                    weighted_tests.append(
                        (test_weight_pair.weight, test_weight_pair.tests))
                modifications[mod.action].extend(
                    weighted_random_item(weighted_tests))
        if not modifications:
            return num_modified
        all_objects = list(services.object_manager().values())
        for obj in all_objects:
            if obj.is_sim:
                continue
            if object_criteria is not None and not object_criteria(obj):
                continue
            resolver = SingleObjectResolver(obj)
            modified = False
            for (action, tests) in modifications.items():
                if not tests.run_tests(resolver):
                    continue
                modified = True
                action_type = action.action_type
                if action_type == ModifyAllLotItems.DESTROY_OBJECT:
                    objects_to_destroy.append(obj)
                    break
                elif action_type == ModifyAllLotItems.SET_STATE:
                    new_state_value = action.action_value
                    if obj.state_component and obj.has_state(
                            new_state_value.state):
                        obj.set_state(new_state_value.state,
                                      new_state_value,
                                      immediate=True)
                        if action_type in (
                                ModifyAllLotItems.INVENTORY_TRANSFER,
                                ModifyAllLotItems.DELIVER_BILLS):
                            element = action.action_value()
                            element._do_behavior()
                        elif action_type == ModifyAllLotItems.SET_ON_FIRE:
                            fire_service = services.get_fire_service()
                            fire_service.spawn_fire_at_object(obj)
                        elif action_type == ModifyAllLotItems.CLEANUP_VEHICLE:
                            if self._should_cleanup_vehicle(obj):
                                objects_to_destroy.append(obj)
                                if action_type == ModifyAllLotItems.LOOT:
                                    for loot_action in action.loot_actions:
                                        loot_action.apply_to_resolver(resolver)
                                    else:
                                        raise NotImplementedError
                                else:
                                    raise NotImplementedError
                        elif action_type == ModifyAllLotItems.LOOT:
                            for loot_action in action.loot_actions:
                                loot_action.apply_to_resolver(resolver)
                            else:
                                raise NotImplementedError
                        else:
                            raise NotImplementedError
                elif action_type in (ModifyAllLotItems.INVENTORY_TRANSFER,
                                     ModifyAllLotItems.DELIVER_BILLS):
                    element = action.action_value()
                    element._do_behavior()
                elif action_type == ModifyAllLotItems.SET_ON_FIRE:
                    fire_service = services.get_fire_service()
                    fire_service.spawn_fire_at_object(obj)
                elif action_type == ModifyAllLotItems.CLEANUP_VEHICLE:
                    if self._should_cleanup_vehicle(obj):
                        objects_to_destroy.append(obj)
                        if action_type == ModifyAllLotItems.LOOT:
                            for loot_action in action.loot_actions:
                                loot_action.apply_to_resolver(resolver)
                            else:
                                raise NotImplementedError
                        else:
                            raise NotImplementedError
                elif action_type == ModifyAllLotItems.LOOT:
                    for loot_action in action.loot_actions:
                        loot_action.apply_to_resolver(resolver)
                    else:
                        raise NotImplementedError
                else:
                    raise NotImplementedError
            if modified:
                num_modified += 1
        for obj in objects_to_destroy:
            obj.destroy(source=self,
                        cause='Destruction requested by modify lot tuning')
        objects_to_destroy = None
        return num_modified

    def _should_cleanup_vehicle(self, obj):
        vehicle_component = obj.get_component(VEHICLE_COMPONENT)
        if vehicle_component is None:
            return False
        household_owner_id = obj.get_household_owner_id()
        if household_owner_id is not None and household_owner_id != 0:
            return False
        elif obj.interaction_refs:
            return False
        return True
Example #17
0
class PortalComponent(Component,
                      HasTunableFactory,
                      AutoFactoryInit,
                      component_name=types.PORTAL_COMPONENT):
    PORTAL_DIRECTION_THERE = 0
    PORTAL_DIRECTION_BACK = 1
    PORTAL_LOCATION_ENTRY = 0
    PORTAL_LOCATION_EXIT = 1
    FACTORY_TUNABLES = {
        '_portal_data':
        TunableList(
            description=
            '\n            The portals that are to be created for this object.\n            ',
            tunable=TunablePortalReference(pack_safe=True)),
        'state_values_which_disable_portals':
        TunableMapping(
            description=
            '\n            A mapping between object state values and portals which should be\n            disabled when those state values are active. Disabling a portal\n            requires a full refresh of the owning objects portals.\n            ',
            key_type=TunableStateValueReference(pack_safe=True),
            value_type=TunableList(tunable=TunablePortalReference(
                pack_safe=True))),
        '_portal_animation_component':
        OptionalTunable(
            description=
            '\n            If enabled, this portal animates in response to agents traversing\n            it. Use Enter/Exit events to control when and for how long an\n            animation plays.\n            ',
            tunable=PortalAnimationComponent.TunableFactory()),
        '_portal_locking_component':
        OptionalTunable(
            description=
            '\n            If enabled then this object will be capable of being locked using\n            the same system as Portal Objects.\n            \n            If not enabled then it will not have a portal locking component\n            and will therefore not be lockable.\n            ',
            tunable=PortalLockingComponent.TunableFactory()),
        '_portal_disallowed_tags':
        TunableTags(
            description=
            '\n            A set of tags used to prevent Sims in particular role states from\n            using this portal.\n            ',
            filter_prefixes=tag.PORTAL_DISALLOWANCE_PREFIX)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._portals = {}
        self._custom_portals = None
        self._enable_refresh = True

    def get_subcomponents_gen(self):
        yield from super().get_subcomponents_gen()
        if self._portal_locking_component is not None:
            portal_locking_component = self._portal_locking_component(
                self.owner)
            yield from portal_locking_component.get_subcomponents_gen()
        if self._portal_animation_component is not None:
            portal_animation_component = self._portal_animation_component(
                self.owner)
            yield from portal_animation_component.get_subcomponents_gen()

    @property
    def refresh_enabled(self):
        return self._enable_refresh

    @refresh_enabled.setter
    def refresh_enabled(self, value):
        self._enable_refresh = bool(value)

    def on_buildbuy_exit(self, *_, **__):
        self._refresh_portals()

    def on_location_changed(self, *_, **__):
        zone = services.current_zone()
        if zone.is_in_build_buy or zone.is_zone_loading:
            return
        self._refresh_portals()

    def finalize_portals(self):
        self._refresh_portals()

    def _refresh_portals(self):
        if self.refresh_enabled:
            self._remove_portals()
            self._add_portals()
            self.owner.refresh_locks()

    def on_add(self, *_, **__):
        services.object_manager().add_portal_to_cache(self.owner)
        if len(self.state_values_which_disable_portals) > 0:
            self.owner.add_state_changed_callback(
                self._on_state_changed_callback)

    def on_remove(self, *_, **__):
        self._remove_portals()
        services.object_manager().remove_portal_from_cache(self.owner)

    @componentmethod
    @sims4.utils.exception_protected(default_return=0)
    def c_api_get_portal_duration(self, portal_id, walkstyle, age, gender,
                                  species):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_duration(portal_id, walkstyle, age,
                                              gender, species)
        return 0

    @componentmethod
    def add_portal_data(self, portal_id, actor, walkstyle):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.add_portal_data(portal_id, actor, walkstyle)

    @componentmethod
    def split_path_on_portal(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.split_path_on_portal()
        return PathSplitType.PathSplitType_DontSplit

    @componentmethod
    def get_posture_change(self, portal_id, initial_posture):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_posture_change(portal_id, initial_posture)
        return (initial_posture, initial_posture)

    @componentmethod
    def provide_route_events(self, portal_id, route_event_context, sim, path,
                             **kwargs):
        if portal_id in self._portals:
            portal = self._portals.get(portal_id)
            return portal.provide_route_events(portal_id, route_event_context,
                                               sim, path, **kwargs)

    @componentmethod
    def add_portal_events(self, portal_id, actor, time, route_pb):
        portal = self._portals.get(portal_id)
        if portal is not None:
            portal.traversal_type.add_portal_events(portal_id, actor,
                                                    self.owner, time, route_pb)
            portal.traversal_type.notify_in_use(actor, portal, self.owner)

    @componentmethod
    def get_portal_asm_params(self, portal_id, sim):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_asm_params(portal_id, sim)
        return {}

    @componentmethod
    def get_portal_owner(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.obj
        return self.owner

    @componentmethod
    def get_target_surface(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_target_surface(portal_id)
        return self.owner.routing_surface

    def _add_portals(self):
        disabled_portals = set()
        if self.state_values_which_disable_portals:
            for (state_value,
                 portals) in self.state_values_which_disable_portals.items():
                if self.owner.state_value_active(state_value):
                    disabled_portals.update(portals)
        for portal_data in self._portal_data:
            if portal_data not in disabled_portals:
                self._add_portal_internal(self.owner, portal_data)
        if self.owner.parts is not None:
            for part in self.owner.parts:
                part_definition = part.part_definition
                for portal_data in part_definition.portal_data:
                    if portal_data not in disabled_portals:
                        self._add_portal_internal(part, portal_data)
        if self._custom_portals is not None:
            for (location_point, portal_data, mask, _) in self._custom_portals:
                self._add_portal_internal(location_point, portal_data, mask)

    def _add_portal_internal(self,
                             obj,
                             portal_data,
                             portal_creation_mask=None):
        portal_instance_ids = []
        for portal in portal_data.get_portal_instances(obj,
                                                       portal_creation_mask):
            if portal.there is not None:
                self._portals[portal.there] = portal
                portal_instance_ids.append(portal.there)
            if portal.back is not None:
                self._portals[portal.back] = portal
                portal_instance_ids.append(portal.back)
        return portal_instance_ids

    def _remove_portal_internal(self, portal_id):
        if portal_id in self._portals:
            remove_portal(portal_id)
            portal = self._portals[portal_id]
            if portal.there is not None and portal.there == portal_id:
                portal.there = None
            elif portal.back is not None:
                if portal.back == portal_id:
                    portal.back = None
            del self._portals[portal_id]

    def _remove_portals(self):
        for portal_id in self._portals:
            remove_portal(portal_id)
        self._portals.clear()
        if self._custom_portals is not None:
            self._custom_portals.clear()
            self._custom_portals = None

    @componentmethod_with_fallback(lambda *_, **__: False)
    def has_portals(self, check_parts=True):
        if self._portal_data or self._custom_portals:
            return True
        elif check_parts and self.owner.parts is not None:
            return any(part.part_definition is not None
                       and part.part_definition.portal_data is not None
                       for part in self.owner.parts)
        return False

    @componentmethod_with_fallback(lambda *_, **__: [])
    def get_portal_pairs(self):
        return set(
            _PortalPair(portal.there, portal.back)
            for portal in self._portals.values())

    @componentmethod_with_fallback(lambda *_, **__: None)
    def get_portal_data(self):
        return self._portal_data

    @componentmethod
    def get_portal_instances(self):
        return frozenset(self._portals.values())

    @componentmethod
    def get_portal_type(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.portal_type
        return PortalType.PortalType_Animate

    @componentmethod
    def update_portal_cache(self, portal, portal_id):
        self._portals[portal_id] = portal

    @componentmethod_with_fallback(lambda *_, **__: None)
    def get_portal_by_id(self, portal_id):
        return self._portals.get(portal_id, None)

    @componentmethod_with_fallback(lambda *_, **__: ())
    def get_dynamic_portal_locations_gen(self):
        for portal_data in self._portal_data:
            yield from portal_data.get_dynamic_portal_locations_gen(self.owner)

    @componentmethod
    def get_single_portal_locations(self):
        portal_pair = next(iter(self._portals.values()), None)
        if portal_pair is not None:
            portal_there = self.get_portal_by_id(portal_pair.there)
            portal_back = self.get_portal_by_id(portal_pair.back)
            front_location = None
            if portal_there is not None:
                front_location = portal_there.there_entry
            back_location = None
            if portal_back is not None:
                back_location = portal_back.back_entry
            return (front_location, back_location)
        return (None, None)

    @componentmethod
    def set_portal_cost_override(self, portal_id, cost, sim=None):
        portal = self._portals.get(portal_id)
        if portal is not None:
            portal.set_portal_cost_override(cost, sim=sim)

    @componentmethod
    def get_portal_cost(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_cost(portal_id)

    @componentmethod
    def get_portal_cost_override(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_cost_override()

    @componentmethod_with_fallback(lambda *_, **__: True)
    def lock_portal_on_use(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.lock_portal_on_use
        return True

    @componentmethod
    def clear_portal_cost_override(self, portal_id, sim=None):
        portal = self._portals.get(portal_id)
        if portal is not None:
            portal.clear_portal_cost_override(sim=sim)

    @componentmethod
    def is_ungreeted_sim_disallowed(self):
        return any(p.is_ungreeted_sim_disallowed()
                   for p in self._portals.values())

    @componentmethod
    def get_portal_disallowed_tags(self):
        return self._portal_disallowed_tags

    @componentmethod
    def get_entry_clothing_change(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_entry_clothing_change(interaction, portal_id,
                                                    **kwargs)

    @componentmethod
    def get_exit_clothing_change(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_exit_clothing_change(interaction, portal_id,
                                                   **kwargs)

    @componentmethod
    def get_on_entry_outfit(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_on_entry_outfit(interaction, portal_id, **kwargs)

    @componentmethod
    def get_on_exit_outfit(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_on_exit_outfit(interaction, portal_id, **kwargs)

    @componentmethod
    def get_gsi_portal_items_list(self, key_name, value_name):
        gsi_portal_items = self.owner.get_gsi_portal_items(
            key_name, value_name)
        return gsi_portal_items

    @componentmethod
    def get_nearest_posture_change(self, sim):
        shortest_dist = sims4.math.MAX_FLOAT
        nearest_portal_id = None
        nearest_portal = None
        sim_position = sim.position
        for (portal_id, portal_instance) in self._portals.items():
            (posture_entry, posture_exit) = portal_instance.get_posture_change(
                portal_id, None)
            if posture_entry is posture_exit:
                continue
            (entry_loc, _) = portal_instance.get_portal_locations(portal_id)
            dist = (entry_loc.position - sim_position).magnitude_squared()
            if not nearest_portal is None:
                if shortest_dist > dist:
                    shortest_dist = dist
                    nearest_portal = portal_instance
                    nearest_portal_id = portal_id
            shortest_dist = dist
            nearest_portal = portal_instance
            nearest_portal_id = portal_id
        if nearest_portal is None:
            return (None, None)
        return nearest_portal.get_posture_change(nearest_portal_id, None)

    @componentmethod_with_fallback(lambda *_, **__: False)
    def has_posture_portals(self):
        for (portal_id, portal_instance) in self._portals.items():
            (posture_entry,
             _) = portal_instance.get_posture_change(portal_id, None)
            if posture_entry is not None:
                return True

    def add_custom_portal(self,
                          location_point,
                          portal_data,
                          portal_creation_mask=None):
        portal_ids = self._add_portal_internal(location_point, portal_data,
                                               portal_creation_mask)
        if portal_ids:
            if self._custom_portals is None:
                self._custom_portals = []
            self._custom_portals.append((location_point, portal_data,
                                         portal_creation_mask, portal_ids))
        return portal_ids

    def remove_custom_portals(self, portal_ids):
        if self._custom_portals is None:
            return
        for custom_portal in list(self._custom_portals):
            (location_point, portal_data, mask,
             custom_portal_ids) = custom_portal
            portal_ids_to_remove = []
            if all(custom_portal_id in portal_ids
                   for custom_portal_id in custom_portal_ids):
                self._custom_portals.remove(custom_portal)
                portal_ids_to_remove = custom_portal_ids
            else:
                portal_ids_to_remove = [
                    custom_portal_id for custom_portal_id in custom_portal_ids
                    if custom_portal_id in portal_ids
                ]
                if portal_ids_to_remove:
                    portal_ids_to_keep = [
                        custom_portal_id
                        for custom_portal_id in custom_portal_ids
                        if custom_portal_id not in portal_ids_to_remove
                    ]
                    self._custom_portals.remove(custom_portal)
                    self._custom_portals.append((location_point, portal_data,
                                                 mask, portal_ids_to_keep))
            for portal_id in portal_ids_to_remove:
                self._remove_portal_internal(portal_id)
        if not self._custom_portals:
            self._custom_portals = None

    def clear_custom_portals(self):
        if self._custom_portals is not None:
            portal_ids_to_remove = [
                portal_id for custom_portal in self._custom_portals
                for portal_id in custom_portal[3]
            ]
            self.remove_custom_portals(portal_ids_to_remove)
            self._custom_portals.clear()
            self._custom_portals = None

    def get_vehicles_nearby_portal_id(self, portal_id):
        object_manager = services.object_manager()
        owner_position = Vector3Immutable(self.owner.position.x, 0,
                                          self.owner.position.z)
        portal_inst = self.get_portal_by_id(portal_id)
        if portal_inst is None:
            return []
        if portal_inst.portal_template.use_vehicle_after_traversal is None:
            return []
        target_surface = portal_inst.get_target_surface(portal_id)
        results = []
        portal_vehicle_tuning = portal_inst.portal_template.use_vehicle_after_traversal
        for vehicle in object_manager.get_objects_with_tags_gen(
                *portal_vehicle_tuning.vehicle_tags):
            if vehicle.routing_surface.type != target_surface.type:
                continue
            vehicle_position = Vector3Immutable(vehicle.position.x, 0,
                                                vehicle.position.z)
            distance = (owner_position - vehicle_position).magnitude_squared()
            if distance > portal_inst.portal_template.use_vehicle_after_traversal.max_distance:
                continue
            results.append(vehicle)
        return results

    def get_portal_location_by_type(self, portal_type, portal_direction,
                                    portal_location):
        portal_pairs = self.get_portal_pairs()
        for (portal_there, portal_back) in portal_pairs:
            if portal_there is None and portal_back is None:
                continue
            there_instance = self.get_portal_by_id(portal_there)
            if there_instance.portal_template is portal_type.value:
                location = self._get_desired_location(portal_there,
                                                      portal_back,
                                                      portal_direction,
                                                      portal_location)
                if location is None:
                    continue
                return location

    def _on_state_changed_callback(self, owner, state, old_value, new_value):
        if old_value == new_value:
            return
        if old_value in self.state_values_which_disable_portals or new_value in self.state_values_which_disable_portals:
            self._refresh_portals()

    def _get_desired_location(self, portal_there_id, portal_back_id,
                              portal_direction, portal_location):
        if portal_direction == PortalComponent.PORTAL_DIRECTION_THERE:
            portal_instance = self.get_portal_by_id(portal_there_id)
        else:
            if portal_back_id is None:
                return
            portal_instance = self.get_portal_by_id(portal_back_id)
            if portal_instance is None:
                return
        location = portal_instance.there_entry if portal_location == PortalComponent.PORTAL_LOCATION_ENTRY else portal_instance.there_exit
        return location
class ObjectRelationshipComponent(
        Component,
        HasTunableFactory,
        AutoFactoryInit,
        component_name=types.OBJECT_RELATIONSHIP_COMPONENT,
        persistence_key=protocols.PersistenceMaster.PersistableData.
        ObjectRelationshipComponent):
    FACTORY_TUNABLES = {
        'number_of_allowed_relationships':
        OptionalTunable(
            description=
            '\n            Number of Sims who can have a relationship with this object at one\n            time.  If not specified, an infinite number of Sims can have a \n            relationship with the object.\n            ',
            tunable=TunableRange(tunable_type=int, default=1, minimum=1)),
        'icon_override':
        OptionalTunable(
            description=
            "\n            If enabled, this will override the object's thumbnail generated \n            default icon on Relationship panel.\n            ",
            tunable=TunableResourceKey(
                description=
                '\n                The icon to be displayed in the Relationship panel.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE,
                export_modes=ExportModes.All)),
        'relationship_stat':
        Statistic.TunableReference(
            description=
            "\n            The statistic which will be created for each of this object's\n            relationships.\n            "
        ),
        'relationship_track_visual':
        OptionalTunable(
            description=
            '\n            If enabled, the relationship track to send the client and where\n            it should be displayed. If this is None then this relationship will \n            not be sent down to the client.\n            ',
            tunable=TunableTuple(
                relationship_track=RelationshipTrack.TunableReference(
                    description=
                    '\n                    The relationship that this track will visually try and imitate in\n                    regards to static track tack data.\n                    '
                ),
                visible_in_relationship_panel=Tunable(
                    description=
                    "\n                    By default the relationship is visible in the relationship \n                    panel and the object's tooltip. If this is set to false, \n                    hide the relationship from the relationship panel. \n                    ",
                    tunable_type=bool,
                    default=True))),
        'relationship_based_state_change_tuning':
        OptionalTunable(
            TunableTuple(
                description=
                '\n            A list of value ranges and associated states.  If the active Sim\n            has a relationship with this object  that falls within one of the\n            value ranges specified here, the object will change state to match\n            the specified state.\n            \n            These state changes exist on a per Sim basis, so this tuning will\n            effectively make the same object appear different depending on\n            which Sim is currently active.\n            ',
                state_changes=TunableList(
                    tunable=TunableTuple(
                        value_threshold=TunableThreshold(
                            description=
                            "\n                        The range that the active Sim's relationship with this\n                        object must fall within in order for this state change to\n                        take place.\n                        "
                        ),
                        state=TunableStateValueReference(
                            description=
                            "\n                        The state this object will change to if it's relationship\n                        with the active Sim falls within the specified range.\n                        "
                        ))),
                default_state=TunableStateValueReference(
                    description=
                    '\n                The state this object will change to if there is no other tuned\n                relationship based state change for the currently active Sim.\n                '
                )))
    }

    def __init__(self, owner, **kwargs):
        super().__init__(owner, **kwargs)
        self._state_changes = None
        self._default_state = None
        self._object_social_mixin = None
        self._relationships = {}
        self._relationship_changed_callbacks = defaultdict(CallableList)
        self._definition_changed_in_buildbuy = False

    @staticmethod
    def setup_relationship(sim, target_object):
        if target_object.objectrelationship_component is None:
            logger.error(
                "Failed to add object relationship because {} doesn't have objectrelationship_component tuned",
                target_object)
            return
        if target_object.objectrelationship_component.has_relationship(sim.id):
            logger.error('Relationship already exists between {} and {}.', sim,
                         target_object)
            return
        if not target_object.objectrelationship_component.add_relationship(
                sim.id):
            logger.error(
                'Failed to add new object relationship between {} and {}.',
                sim, target_object)

    @property
    def relationships(self):
        return self._relationships

    def get_number_of_allowed_relationships(self):
        return self.number_of_allowed_relationships

    def _on_active_sim_change(self, _, new_sim):
        if new_sim is None:
            return
        relationship = self._get_relationship(new_sim.id)
        self._update_state(relationship)

    def _update_state(self, relationship):
        if self._default_state is None:
            return
        if relationship is None:
            new_state = self._default_state
        elif self._state_changes is None:
            new_state = self._default_state
        else:
            for state_change in self._state_changes:
                if state_change.value_threshold.compare(
                        relationship.get_value()):
                    new_state = state_change.state
                    break
            else:
                new_state = self._default_state
        self.owner.set_state(new_state.state, new_state)

    @property
    def _can_add_new_relationship(self):
        if self.number_of_allowed_relationships is not None and len(
                self._relationships) >= self.number_of_allowed_relationships:
            return False
        return True

    def on_add(self):
        services.current_zone().register_callback(
            zone_types.ZoneState.HOUSEHOLDS_AND_SIM_INFOS_LOADED,
            self._publish_relationship_data)
        if self.relationship_based_state_change_tuning is None:
            return
        self._state_changes = self.relationship_based_state_change_tuning.state_changes
        self._default_state = self.relationship_based_state_change_tuning.default_state
        services.current_zone().register_callback(
            zone_types.ZoneState.CLIENT_CONNECTED,
            self._register_active_sim_change)

    def on_remove(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.unregister_active_sim_changed(self._on_active_sim_change)
        self.owner.remove_name_changed_callback(self._on_name_changed)
        self.destroy_all_relationship()

    def apply_definition(self, definition, obj_state=0):
        if not services.current_zone().is_in_build_buy:
            return
        self._definition_changed_in_buildbuy |= self.owner.definition != definition

    def on_buildbuy_exit(self):
        if not self._definition_changed_in_buildbuy:
            return
        self._publish_relationship_data()
        self._definition_changed_in_buildbuy = False

    def _register_active_sim_change(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.register_active_sim_changed(self._on_active_sim_change)

    def _publish_relationship_data(self):
        if self.relationship_track_visual is None:
            return
        for sim_id in self._relationships.keys():
            self._send_relationship_data(sim_id)

    def _update_object_relationship_name(self):
        ownable_component = self.owner.get_component(types.OWNABLE_COMPONENT)
        if ownable_component is not None:
            sim_owner_id = ownable_component.get_sim_owner_id()
            obj_def_id = self.owner.definition.id
            relationship_service = services.relationship_service()
            obj_tag_set = relationship_service.get_mapped_tag_set_of_id(
                obj_def_id)
            if obj_tag_set is not None:
                obj_relationship = relationship_service.get_object_relationship(
                    sim_owner_id, obj_tag_set)
                if obj_relationship is not None and self.owner.has_custom_name(
                ):
                    obj_relationship.set_object_rel_name(
                        self.owner.custom_name)

    def _on_name_changed(self, *_, **__):
        self._publish_relationship_data()
        self._update_object_relationship_name()

    def add_relationship_changed_callback_for_sim_id(self, sim_id, callback):
        self._relationship_changed_callbacks[sim_id].append(callback)

    def remove_relationship_changed_callback_for_sim_id(
            self, sim_id, callback):
        if sim_id in self._relationship_changed_callbacks and callback in self._relationship_changed_callbacks[
                sim_id]:
            self._relationship_changed_callbacks[sim_id].remove(callback)

    def _trigger_relationship_changed_callbacks_for_sim_id(self, sim_id):
        callbacks = self._relationship_changed_callbacks[sim_id]
        if callbacks is not None:
            callbacks()

    def add_relationship(self, sim_id):
        if sim_id in self._relationships:
            return False
        if not self._can_add_new_relationship:
            return False
        self.owner.on_hovertip_requested()
        stat = self.relationship_stat(None)
        self._relationships[sim_id] = stat
        stat.on_add()
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        self.owner.add_name_changed_callback(self._on_name_changed)
        return True

    def remove_relationship(self, sim_id):
        if sim_id not in self._relationships:
            return
        del self._relationships[sim_id]
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        self._send_relationship_destroy(sim_id)

    def destroy_all_relationship(self):
        sim_ids = list(self._relationships.keys())
        for sim_id in sim_ids:
            self.remove_relationship(sim_id)

    def modify_relationship(self, sim_id, value, add=True, from_load=False):
        if sim_id not in self._relationships:
            if not add:
                return
            if not self.add_relationship(sim_id):
                return
        if from_load:
            self._relationships[sim_id].set_value(value)
        else:
            self._relationships[sim_id].add_value(value)
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        client = services.client_manager().get_first_client()
        if client is not None and client.active_sim is not None and client.active_sim.sim_id == sim_id:
            self._update_state(self._relationships[sim_id])

    def on_social_start(self, sim):
        if self.has_relationship(sim.id) or self.add_relationship(sim.id):
            self._object_social_mixin = ObjectRelationshipSocialMixin(
                sim, self.owner.id, self._get_relationship(sim.id))
            self._object_social_mixin.send_social_start_message()

    def on_social_end(self):
        if self._object_social_mixin is not None:
            self._object_social_mixin.send_social_end_message()
            self._object_social_mixin = None

    def _get_relationship(self, sim_id):
        return self._relationships.get(sim_id)

    def has_relationship(self, sim_id):
        return sim_id in self._relationships

    def get_relationship_value(self, sim_id):
        relationship = self._get_relationship(sim_id)
        if relationship is not None:
            return relationship.get_value()
        return self.relationship_stat.initial_value

    def get_relationship_initial_value(self):
        return self.relationship_stat.initial_value

    def get_relationship_max_value(self):
        return self.relationship_stat.max_value

    def get_relationship_min_value(self):
        return self.relationship_stat.min_value

    def _send_relationship_data(self, sim_id):
        if self.relationship_track_visual is None:
            return
        relationship_to_send = self._get_relationship(sim_id)
        if not relationship_to_send:
            return
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return
        msg = commodity_protocol.RelationshipUpdate()
        msg.actor_sim_id = sim_id
        (msg.target_id.object_id,
         msg.target_id.manager_id) = self.owner.icon_info
        msg.target_instance_id = self.owner.id
        if self.icon_override is not None:
            build_icon_info_msg(IconInfoData(icon_resource=self.icon_override),
                                None, msg.target_icon_override)
        with ProtocolBufferRollback(msg.tracks) as relationship_track_update:
            relationship_value = relationship_to_send.get_value()
            relationship_track_update.track_score = relationship_value
            relationship_track_update.track_bit_id = self.relationship_track_visual.relationship_track.get_bit_at_relationship_value(
                relationship_value).guid64
            relationship_track_update.track_id = self.relationship_track_visual.relationship_track.guid64
            relationship_track_update.track_popup_priority = self.relationship_track_visual.relationship_track.display_popup_priority
            relationship_track_update.visible_in_relationship_panel = self.relationship_track_visual.visible_in_relationship_panel
        send_relationship_op(sim_info, msg)
        if self._object_social_mixin is not None:
            self._object_social_mixin.send_social_update_message()

    def _send_relationship_destroy(self, sim_id):
        if self.relationship_track_visual is None or self.relationship_track_visual.relationship_track is None:
            return
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return
        msg = commodity_protocol.RelationshipDelete()
        msg.actor_sim_id = sim_id
        msg.target_id = self.owner.id
        op = GenericProtocolBufferOp(
            DistributorOps_pb2.Operation.SIM_RELATIONSHIP_DELETE, msg)
        distributor = Distributor.instance()
        distributor.add_op(services.sim_info_manager().get(sim_id), op)

    def save(self, persistence_master_message):
        if not self._relationships:
            return
        persistable_data = protocols.PersistenceMaster.PersistableData()
        persistable_data.type = protocols.PersistenceMaster.PersistableData.ObjectRelationshipComponent
        relationship_component_data = persistable_data.Extensions[
            protocols.PersistableObjectRelationshipComponent.persistable_data]
        for (key, value) in self._relationships.items():
            if not value.persisted_tuning:
                continue
            with ProtocolBufferRollback(relationship_component_data.
                                        relationships) as relationship_data:
                relationship_data.sim_id = key
                relationship_data.value = value.get_value()
        persistence_master_message.data.extend([persistable_data])

    def load(self, persistable_data):
        relationship_component_data = persistable_data.Extensions[
            protocols.PersistableObjectRelationshipComponent.persistable_data]
        for relationship in relationship_component_data.relationships:
            self.modify_relationship(relationship.sim_id,
                                     relationship.value,
                                     from_load=True)
Example #19
0
class MannequinComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=MANNEQUIN_COMPONENT, persistence_key=SimObjectAttributes_pb2.PersistenceMaster.PersistableData.MannequinComponent):
    MANNEQUIN_GROUP_SHARING_WARNING_NOTIFICATION = TunableUiDialogNotificationSnippet(description='\n        A notification to show explaining how outfit merging will clobber\n        outfits on the mannequin being placed into the world.\n        ', pack_safe=True)

    class _MannequinGroup(DynamicEnum):
        INVALID = 0

    class _MannequinTemplateExplicit(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {'age': TunableEnumEntry(description='\n                The default age of this object when placed from Build/Buy.\n                ', tunable_type=Age, default=Age.ADULT), 'gender': TunableEnumEntry(description='\n                The default gender of this object when placed from\n                Build/Buy.\n                ', tunable_type=Gender, default=Gender.MALE), 'skin_tone': TunableSkinTone(description='\n                The default skin tone of this object when placed from Build/Buy.\n                ')}

        def create_sim_info_data(self, sim_id):
            return SimInfoBaseWrapper(sim_id=sim_id, age=self.age, gender=self.gender, species=self.species, skin_tone=self.skin_tone)

    class _MannequinTemplateResource(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {'resource_key': TunableResourceKey(description='\n                The SimInfo file to use.\n                ', default=None, resource_types=(sims4.resources.Types.SIMINFO,)), 'outfit': OptionalTunable(description='\n                If enabled, the mannequin will default to the specified outfit.\n                ', tunable=TunableTuple(description='\n                    The outfit to switch the mannequin into.\n                    ', outfit_category=TunableEnumEntry(description='\n                        The outfit category.\n                        ', tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY), outfit_index=TunableRange(description='\n                        The outfit index.\n                        ', tunable_type=int, minimum=0, maximum=4, default=0)))}

        def create_sim_info_data(self, sim_id):
            sim_info_data = SimInfoBaseWrapper(sim_id=sim_id)
            sim_info_data.load_from_resource(self.resource_key)
            if self.outfit is not None:
                outfit_to_set = (self.outfit.outfit_category, self.outfit.outfit_index)
                if sim_info_data.has_outfit(outfit_to_set):
                    sim_info_data.set_current_outfit(outfit_to_set)
                    sim_info_data.set_previous_outfit(outfit_to_set, force=True)
            return sim_info_data

    FACTORY_TUNABLES = {'template': TunableVariant(description='\n            Define how the initial SimInfo data for this mannequin is created.\n            ', explicit=_MannequinTemplateExplicit.TunableFactory(), resource=_MannequinTemplateResource.TunableFactory(), default='resource'), 'cap_modifier': TunableRange(description='\n            This mannequin will be worth this many Sims when computing the cap\n            limit for NPCs in the world. While mannequins are not simulating\n            entities, they might have rendering costs that are equivalent to\n            those of Sims. We therefore need to limit how many of them are in\n            the world.\n            \n            Please consult Client Systems or CAS before changing this to a lower\n            number, as it might negatively impact performance, especially on\n            Minspec.\n            ', tunable_type=float, default=0.5, minimum=0, needs_tuning=False), 'outfit_sharing': OptionalTunable(description='\n            If enabled, all mannequins sharing the same group, age, and gender\n            will share outfits. Objects placed from the Gallery or the household\n            inventory will add any unique outfits to the master list, but will\n            lose any outfit beyond the maximum per category.\n            ', tunable=TunableEnumEntry(description='\n                The enum that controls how mannequins share outfits.\n                ', tunable_type=_MannequinGroup, default=_MannequinGroup.INVALID)), 'outfit_states': TunableMapping(description='\n            A mapping of outfit category to states. When the mannequin is\n            wearing the specified outfit category, it will transition into the\n            specified state.\n            ', key_type=TunableEnumEntry(description='\n                The outfit category that will trigger the associated state\n                change.\n                ', tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY), value_type=TunableStateValueReference(description='\n                The state of the object when the mannequin is wearing the\n                associated outfit category.\n                ')), 'outfit_modifiers': TunableMapping(description='\n            A mapping of modifiers to apply to the mannequin while in the\n            specified state.\n            ', key_type=TunableStateValueReference(description='\n                The state that triggers these outfit modifiers.\n                '), value_type=AppearanceModifier.TunableFactory(description='\n                An appearance modifier to apply while this state is active.\n                ')), 'state_trigger_grubby': TunableStateValueReference(description='\n            The state that triggers the mannequin being grubby. Any other state\n            on this track will set the mannequin as not grubby.\n            ', allow_none=True), 'state_trigger_singed': TunableStateValueReference(description='\n            The state that triggers the mannequin being singed. Any other state\n            on this track will set the mannequin as not singed.\n            ', allow_none=True)}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sim_info_data = None
        self._pose = None
        self._is_grubby = False
        self._is_singed = False

    @property
    def mannequin_id(self):
        return self.owner.id

    @distributor.fields.ComponentField(op=distributor.ops.ChangeSimOutfit)
    def mannequin_outfit(self):
        return self._sim_info_data.get_current_outfit()

    _resend_mannequin_outfit = mannequin_outfit.get_resend()

    @distributor.fields.ComponentField(op=distributor.ops.SetMannequinPose)
    def mannequin_pose(self):
        return self._pose

    @mannequin_pose.setter
    def mannequin_pose(self, value):
        self._pose = value

    @distributor.fields.ComponentField(op=distributor.ops.PreloadSimOutfit)
    def mannequin_outfit_preload_list(self):
        return self._sim_info_data.preload_outfit_list

    _resend_mannequin_outfit_preload_list = mannequin_outfit_preload_list.get_resend()

    @distributor.fields.ComponentField(op=distributor.ops.SetMannequinData, priority=distributor.fields.Field.Priority.HIGH)
    def sim_info_data(self):
        appearance_override_sim_info = self._sim_info_data.appearance_tracker.appearance_override_sim_info
        if appearance_override_sim_info is not None:
            return appearance_override_sim_info
        return self._sim_info_data

    _resend_sim_info_data = sim_info_data.get_resend()

    @distributor.fields.ComponentField(op=distributor.ops.SetGrubby)
    def mannequin_is_grubby(self):
        return self._is_grubby

    @mannequin_is_grubby.setter
    def mannequin_is_grubby(self, value):
        self._is_grubby = value

    @distributor.fields.ComponentField(op=distributor.ops.SetSinged)
    def mannequin_is_singed(self):
        return self._is_singed

    @mannequin_is_singed.setter
    def mannequin_is_singed(self, value):
        self._is_singed = value

    @componentmethod
    def get_current_outfit(self):
        return self._sim_info_data.get_current_outfit()

    @componentmethod
    def get_previous_outfit(self):
        return self._sim_info_data.get_previous_outfit()

    @componentmethod
    def get_outfits(self):
        return self._sim_info_data.get_outfits()

    @componentmethod
    def set_current_outfit(self, outfit):
        return self._sim_info_data.set_current_outfit(outfit)

    def _on_outfit_change(self, *_, **__):
        self._resend_mannequin_outfit()
        self._update_outfit_state()

    def _on_outfit_generated(self, outfit_category, outfit_index):
        self._sim_info_data.set_outfit_flags(outfit_category, outfit_index, BodyTypeFlag.CLOTHING_ALL)
        mannequin_group_data = get_mannequin_group_data(self.outfit_sharing, self._sim_info_data)
        mannequin_group_data.set_mannequin_data(self._sim_info_data)
        mannequin_group_data.reconcile_mannequin_data()

    def _on_preload_outfits_changed(self):
        self._resend_mannequin_outfit_preload_list()

    def _update_outfit_state(self):
        if self.owner.id:
            outfit_category = self.get_current_outfit()[0]
            outfit_state = self.outfit_states.get(outfit_category)
            if outfit_state is not None:
                self.owner.set_state(outfit_state.state, outfit_state)

    def on_add(self, *_, **__):
        sim_spawner_service = services.sim_spawner_service()
        sim_spawner_service.add_npc_cap_modifier(self.cap_modifier)
        zone = services.current_zone()
        if not zone.is_zone_loading and not self.owner.is_downloaded:
            self.reconcile_mannequin_data(is_add=True)
        self._update_outfit_state()

    def on_remove(self, *_, **__):
        sim_spawner_service = services.sim_spawner_service()
        sim_spawner_service.add_npc_cap_modifier(-self.cap_modifier)
        if self._sim_info_data is not None:
            self._sim_info_data.on_outfit_changed.remove(self._on_outfit_change)
            self._sim_info_data.on_outfit_generated.remove(self._on_outfit_generated)
            self._sim_info_data.on_preload_outfits_changed.remove(self._on_preload_outfits_changed)

    def on_state_changed(self, state, old_value, new_value, from_init):
        old_appearance_modifier = self.outfit_modifiers.get(old_value)
        if old_appearance_modifier is not None:
            self._sim_info_data.appearance_tracker.remove_appearance_modifiers(state, source=self)
        new_appearance_modifier = self.outfit_modifiers.get(new_value)
        if new_appearance_modifier is not None:
            self._sim_info_data.appearance_tracker.add_appearance_modifiers(new_appearance_modifier.appearance_modifiers, state, new_appearance_modifier.priority, new_appearance_modifier.apply_to_all_outfits, source=self)
        if self.state_trigger_singed is not None:
            if state is self.state_trigger_singed.state:
                if new_value is self.state_trigger_singed:
                    self.mannequin_is_singed = True
                else:
                    self.mannequin_is_singed = False
        if self.state_trigger_grubby is not None:
            if state is self.state_trigger_grubby.state:
                if new_value is self.state_trigger_grubby:
                    self.mannequin_is_grubby = True
                else:
                    self.mannequin_is_grubby = False
        self._resend_sim_info_data()
        self._resend_mannequin_outfit()

    def pre_add(self, manager, obj_id):
        self._sim_info_data = self.template.create_sim_info_data(obj_id)
        self._sim_info_data.on_outfit_changed.append(self._on_outfit_change)
        self._sim_info_data.on_outfit_generated.append(self._on_outfit_generated)
        self._sim_info_data.on_preload_outfits_changed.append(self._on_preload_outfits_changed)
        if self.outfit_sharing is not None:
            mannequin_group_data = get_mannequin_group_data(self.outfit_sharing, self._sim_info_data)
            mannequin_group_data.add_mannequin(self)

    def populate_sim_info_data_proto(self, sim_info_data_msg):
        sim_info_data_msg.mannequin_id = self.mannequin_id
        self._sim_info_data.save_sim_info(sim_info_data_msg)
        if self._pose is not None:
            sim_info_data_msg.animation_pose.asm = get_protobuff_for_key(self._pose.asm)
            sim_info_data_msg.animation_pose.state_name = self._pose.state_name

    def save(self, persistence_master_message):
        persistable_data = SimObjectAttributes_pb2.PersistenceMaster.PersistableData()
        persistable_data.type = SimObjectAttributes_pb2.PersistenceMaster.PersistableData.MannequinComponent
        mannequin_component_data = persistable_data.Extensions[SimObjectAttributes_pb2.PersistableMannequinComponent.persistable_data]
        if self._sim_info_data is not None:
            self.populate_sim_info_data_proto(mannequin_component_data.sim_info_data)
        persistence_master_message.data.extend((persistable_data,))

    def load(self, persistable_data):
        sim_info_data_proto = None
        persistence_service = services.get_persistence_service()
        if persistence_service is not None:
            sim_info_data_proto = persistence_service.get_mannequin_proto_buff(self.mannequin_id)
            if sim_info_data_proto is not None and self.outfit_sharing is not None:
                set_mannequin_group_data_reference(self.outfit_sharing, self._sim_info_data)
        if sim_info_data_proto is None:
            mannequin_component_data = persistable_data.Extensions[SimObjectAttributes_pb2.PersistableMannequinComponent.persistable_data]
            if mannequin_component_data.HasField('sim_info_data'):
                sim_info_data_proto = mannequin_component_data.sim_info_data
        if sim_info_data_proto is not None:
            self._sim_info_data.load_sim_info(sim_info_data_proto)
            persistence_service.del_mannequin_proto_buff(self.mannequin_id)
        zone = services.current_zone()
        if not zone.is_zone_loading:
            self.reconcile_mannequin_data(is_add=True, is_loaded=True)

    def on_finalize_load(self):
        self.reconcile_mannequin_data()

    def _replace_outfits(self, sim_info_data):
        current_outfit = self.get_current_outfit()
        default_outfit = (OutfitCategory.BATHING, 0)
        for (outfit_category, outfit_list) in sim_info_data.get_all_outfits():
            if outfit_category not in REGULAR_OUTFIT_CATEGORIES:
                continue
            self._sim_info_data.remove_outfits_in_category(outfit_category)
            for (outfit_index, outfit_data) in enumerate(outfit_list):
                source_outfit = (outfit_category, outfit_index)
                destination_outfit = self._sim_info_data.add_outfit(outfit_category, outfit_data)
                self._sim_info_data.generate_merged_outfit(sim_info_data, destination_outfit, default_outfit, source_outfit, preserve_outfit_flags=True)
        if self._sim_info_data.has_outfit(current_outfit):
            self._sim_info_data.set_current_outfit(current_outfit)
        else:
            self._sim_info_data.set_current_outfit(default_outfit)

    def _resend_mannequin_data(self):
        self._resend_sim_info_data()
        self._resend_mannequin_outfit()

    def reconcile_mannequin_data(self, *args, **kwargs):
        self.reconcile_mannequin_data_internal(*args, **kwargs)
        enable_mannequin_group_sharing_warning_notification()
        self._resend_mannequin_data()

    def reconcile_mannequin_data_internal(self, is_add=False, is_loaded=False):
        if self.outfit_sharing is None:
            return
        mannequin_group_sharing_mode = get_mannequin_group_sharing_mode()
        mannequin_group_data = get_mannequin_group_data(self.outfit_sharing, self._sim_info_data)
        if is_loaded:
            if mannequin_group_sharing_mode == MannequinGroupSharingMode.ACCEPT_THEIRS:
                mannequin_group_data.set_mannequin_data(self._sim_info_data)
                for mannequin in mannequin_group_data:
                    if mannequin is not self:
                        mannequin._replace_outfits(self._sim_info_data)
                        mannequin._resend_mannequin_data()
                return
            if mannequin_group_sharing_mode == MannequinGroupSharingMode.ACCEPT_YOURS:
                self._replace_outfits(mannequin_group_data.get_mannequin_data())
                return
        mannequin_data = mannequin_group_data.get_mannequin_data()
        if mannequin_data is None:
            mannequin_group_data.set_mannequin_data(self._sim_info_data)
            if is_add:
                mannequin_group_data.reconcile_mannequin_data()
                return
        else:
            if is_add:
                if mannequin_group_sharing_mode == MannequinGroupSharingMode.ACCEPT_MERGED:
                    for (outfit_category, outfit_list) in self._sim_info_data.get_all_outfits():
                        if outfit_category not in REGULAR_OUTFIT_CATEGORIES:
                            continue
                        for (outfit_index, outfit_data) in enumerate(outfit_list):
                            if mannequin_data.is_generated_outfit_duplicate_in_category(self._sim_info_data, (outfit_category, outfit_index)):
                                continue
                            outfits_in_category = mannequin_data.get_outfits_in_category(outfit_category)
                            if outfits_in_category is not None and len(outfits_in_category) >= get_maximum_outfits_for_category(outfit_category):
                                show_mannequin_group_sharing_warning_notification()
                            else:
                                mannequin_data.generate_merged_outfit(self._sim_info_data, mannequin_data.add_outfit(outfit_category, outfit_data), mannequin_data.get_current_outfit(), (outfit_category, outfit_index), preserve_outfit_flags=True)
                mannequin_group_data.reconcile_mannequin_data()
                return
            if self.owner.id:
                for outfit_category in REGULAR_OUTFIT_CATEGORIES:
                    self._sim_info_data.generate_merged_outfits_for_category(mannequin_data, outfit_category, preserve_outfit_flags=True)
 def __init__(self, description='A condition to determine if an object is in a particular state.', **kwargs):
     super().__init__(subject=TunableEnumEntry(ParticipantType, ParticipantType.Object, description='The subject to check the condition on.'), state=TunableVariant(description='The state to check for.', on_trigger=TunableStateValueReference(description='Satisfy the condition when this state is triggered.'), on_boundary=TunableTuple(description='Satisfy the condition when a boundary of this stat-based state is reached', state=TunableStateValueReference(class_restrictions=CommodityBasedObjectStateValue, description='The state required to satisfy the condition'), boundary=TunableVariant(description='The boundary required to be reached for the condition to be satisfied.', locked_args={'upper': True, 'lower': False}, default='upper')), default='on_trigger'), **kwargs)
Example #21
0
class DebugSetupLotInteraction(TerrainImmediateSuperInteraction):
    INSTANCE_TUNABLES = {
        'setup_lot_destroy_old_objects':
        Tunable(bool,
                False,
                description=
                'Destroy objects previously created by this interaction.'),
        'setup_lot_objects':
        TunableList(
            TunableTuple(
                definition=TunableReference(definition_manager()),
                position=TunableVector2(Vector2.ZERO()),
                angle=TunableRange(int, 0, -360, 360),
                children=TunableList(
                    TunableTuple(
                        definition=TunableReference(
                            definition_manager(),
                            description=
                            'The child object to create.  It will appear in the first available slot in which it fits, subject to additional restrictions specified in the other values of this tuning.'
                        ),
                        part_index=OptionalTunable(
                            Tunable(
                                int,
                                0,
                                description=
                                'If specified, restrict slot selection to the given part index.'
                            )),
                        bone_name=OptionalTunable(
                            Tunable(
                                str,
                                '_ctnm_chr_',
                                description=
                                'If specified, restrict slot selection to one with this exact bone name.'
                            )),
                        slot_type=OptionalTunable(
                            TunableReference(
                                manager=services.get_instance_manager(
                                    Types.SLOT_TYPE),
                                description=
                                'If specified, restrict slot selection to ones that support this type of slot.'
                            )),
                        init_state_values=TunableList(
                            description=
                            '\n                                List of states the children object will be set to.\n                                ',
                            tunable=TunableStateValueReference()))),
                init_state_values=TunableList(
                    description=
                    '\n                    List of states the created object will be pushed to.\n                    ',
                    tunable=TunableStateValueReference())))
    }
    _zone_to_cls_to_created_objects = WeakKeyDictionary()

    @classproperty
    def destroy_old_objects(cls):
        return cls.setup_lot_destroy_old_objects

    @classproperty
    def created_objects(cls):
        created_objects = cls._zone_to_cls_to_created_objects.setdefault(
            services.current_zone(), {})
        return setdefault_callable(created_objects, cls, WeakSet)

    def _run_interaction_gen(self, timeline):
        with supress_posture_graph_build():
            if self.destroy_old_objects:
                while self.created_objects:
                    obj = self.created_objects.pop()
                    obj.destroy(
                        source=self,
                        cause='Destroying old objects in setup debug lot.')
            position = self.context.pick.location
            self.spawn_objects(position)
        return True
        yield

    def _create_object(self,
                       definition_id,
                       position=Vector3.ZERO(),
                       orientation=Quaternion.IDENTITY(),
                       level=0,
                       owner_id=0):
        obj = objects.system.create_object(definition_id)
        if obj is not None:
            transform = Transform(position, orientation)
            location = Location(transform, self.context.pick.routing_surface)
            obj.location = location
            obj.set_household_owner_id(owner_id)
            self.created_objects.add(obj)
        return obj

    def spawn_objects(self, position):
        root = sims4.math.Vector3(position.x, position.y, position.z)
        zone = services.current_zone()
        lot = zone.lot
        owner_id = lot.owner_household_id
        if not self.contained_in_lot(lot, root):
            closest_point = self.find_nearest_point_on_lot(lot, root)
            if closest_point is None:
                return False
            radius = (self.top_right_pos -
                      self.bottom_left_pos).magnitude_2d() / 2
            root = closest_point + sims4.math.vector_normalize(
                sims4.math.vector_flatten(lot.center) -
                closest_point) * (radius + 1)
            if not self.contained_in_lot(lot, root):
                sims4.log.warn(
                    'Placement',
                    "Placed the lot objects but the entire bounding box isn't inside the lot. This is ok. If you need them to be inside the lot run the interaction again at a diffrent location."
                )

        def _generate_vector(offset_x, offset_z):
            ground_obj = services.terrain_service.terrain_object()
            ret_vector = sims4.math.Vector3(root.x + offset_x, root.y,
                                            root.z + offset_z)
            ret_vector.y = ground_obj.get_height_at(ret_vector.x, ret_vector.z)
            return ret_vector

        def _generate_quat(rot):
            return sims4.math.Quaternion.from_axis_angle(
                rot, sims4.math.Vector3(0, 1, 0))

        for info in self.setup_lot_objects:
            new_pos = _generate_vector(info.position.x, info.position.y)
            new_rot = _generate_quat(sims4.math.PI / 180 * info.angle)
            new_obj = self._create_object(info.definition,
                                          new_pos,
                                          new_rot,
                                          owner_id=owner_id)
            if new_obj is None:
                sims4.log.error('SetupLot', 'Unable to create object: {}',
                                info)
            else:
                for state_value in info.init_state_values:
                    new_obj.set_state(state_value.state, state_value)
                for child_info in info.children:
                    slot_owner = new_obj
                    if child_info.part_index is not None:
                        for obj_part in new_obj.parts:
                            if obj_part.subroot_index == child_info.part_index:
                                slot_owner = obj_part
                                break
                    bone_name_hash = None
                    if child_info.bone_name is not None:
                        bone_name_hash = hash32(child_info.bone_name)
                    slot_type = None
                    if child_info.slot_type is not None:
                        slot_type = child_info.slot_type
                    for runtime_slot in slot_owner.get_runtime_slots_gen(
                            slot_types={slot_type},
                            bone_name_hash=bone_name_hash):
                        if runtime_slot.is_valid_for_placement(
                                definition=child_info.definition):
                            break
                    else:
                        sims4.log.error(
                            'SetupLot',
                            'Unable to find slot for child object: {}',
                            child_info)
                    child = self._create_object(child_info.definition,
                                                owner_id=owner_id)
                    if child is None:
                        sims4.log.error('SetupLot',
                                        'Unable to create child object: {}',
                                        child_info)
                    else:
                        runtime_slot.add_child(child)
                        for state_value in child_info.init_state_values:
                            child.set_state(state_value.state, state_value)

    def contained_in_lot(self, lot, root):
        self.find_corner_points(root)
        return True

    def find_corner_points(self, root):
        max_x = 0
        min_x = 0
        max_z = 0
        min_z = 0
        for info in self.setup_lot_objects:
            if info.position.x > max_x:
                max_x = info.position.x
            if info.position.x < min_x:
                min_x = info.position.x
            if info.position.y > max_z:
                max_z = info.position.y
            if info.position.y < min_z:
                min_z = info.position.y
        self.top_right_pos = sims4.math.Vector3(root.x + max_x, root.y,
                                                root.z + max_z)
        self.bottom_right_pos = sims4.math.Vector3(root.x + max_x, root.y,
                                                   root.z + min_z)
        self.top_left_pos = sims4.math.Vector3(root.x + min_x, root.y,
                                               root.z + max_z)
        self.bottom_left_pos = sims4.math.Vector3(root.x + min_x, root.y,
                                                  root.z + min_z)

    def find_nearest_point_on_lot(self, lot, root):
        lot_corners = lot.corners
        segments = [(lot_corners[0], lot_corners[1]),
                    (lot_corners[1], lot_corners[2]),
                    (lot_corners[2], lot_corners[3]),
                    (lot_corners[3], lot_corners[1])]
        dist = 0
        closest_point = None
        for segment in segments:
            new_point = sims4.math.get_closest_point_2D(segment, root)
            new_distance = (new_point - root).magnitude()
            if dist == 0:
                dist = new_distance
                closest_point = new_point
            elif new_distance < dist:
                dist = new_distance
                closest_point = new_point
        return closest_point
class SpawnerTuning(HasTunableReference,
                    HasTunableSingletonFactory,
                    AutoFactoryInit,
                    metaclass=TunedInstanceMetaclass,
                    manager=services.get_instance_manager(
                        sims4.resources.Types.RECIPE)):
    __qualname__ = 'SpawnerTuning'
    GROUND_SPAWNER = 1
    SLOT_SPAWNER = 2
    INTERACTION_SPAWNER = 3
    INSTANCE_TUNABLES = {
        'object_reference':
        TunableList(
            description=
            '\n            List of objects the spawner can create.  When the random check \n            picks this value from the weight calculation it will give all\n            the items tuned on this list.\n            ',
            tunable=TunableReference(
                description=
                '\n                Object reference to the object the spawner will give.\n                ',
                manager=services.definition_manager())),
        'spawn_weight':
        TunableRange(
            description=
            '\n            Weight that object will have on the probability calculation \n            of which object to spawn.\n            ',
            tunable_type=int,
            default=1,
            minimum=0),
        'spawner_option':
        TunableVariant(
            description=
            '\n            Type of spawners to create:\n            Ground type - Spawned object will appear on the floor at a tunable \n            radius from the spawner object.\n            Slot type - Spawned object will appear on an available slot of \n            a tunable slot type in the spawner object.\n            Interaction type - Spawned objects will appear on the inventory\n            when player makes a gather-harvest-scavenge interaction on them. \n            ',
            ground_spawning=TunableTuple(
                radius=TunableRange(
                    description=
                    '\n                    Max radius at which the spawned object should appear\n                    ',
                    tunable_type=int,
                    default=1,
                    minimum=0),
                force_states=TunableList(
                    description=
                    '\n                    List of states the created object will be pushed to.\n                    ',
                    tunable=TunableStateValueReference()),
                force_initialization_spawn=OptionalTunable(
                    description=
                    '\n                    If checked, objects with this component will force a \n                    spawning of objects on initialization.  This is mainly used\n                    for objects on the open street where we want to fake that \n                    some time has already passed.  \n                    Additionally, if checked, objects will force the states\n                    on this list instead of the force_states list on the \n                    general spawner tuning, this way we can add some custom\n                    states only for the initialization spawn.\n                    ',
                    tunable=TunableList(
                        description=
                        '\n                        List of states the created object will have when\n                        initialized.\n                        ',
                        tunable=TunableStateValueReference())),
                location_test=TunableTuple(
                    is_outside=OptionalTunable(
                        description=
                        '\n                        If checked, will verify if the spawned object is \n                        located outside. \n                        If unchecked will test the object is not outside\n                        ',
                        disabled_name="Don't_Test",
                        tunable=Tunable(bool, True)),
                    is_natural_ground=OptionalTunable(
                        description=
                        '\n                        If checked, will verify the spawned object is on \n                        natural ground.\n                        If unchecked will test the object is not on natural \n                        ground\n                        ',
                        disabled_name="Don't_Test",
                        tunable=Tunable(bool, True))),
                locked_args={'spawn_type': GROUND_SPAWNER}),
            slot_spawning=TunableTuple(
                slot_type=TunableReference(
                    description=
                    '\n                    Slot type where spawned objects should appear\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        SLOT_TYPE)),
                state_mapping=TunableMapping(
                    description=
                    '\n                    Mapping of states from the spawner object into the possible\n                    states that the spawned object may have\n                    ',
                    key_type=TunableStateValueReference(),
                    value_type=TunableList(
                        description=
                        '\n                        List of possible children for a parent state\n                        ',
                        tunable=TunableTuple(
                            description=
                            '\n                            Pair of weight and possible state that the spawned \n                            object may have\n                            ',
                            weight=TunableRange(
                                description=
                                '\n                                Weight that object will have on the probability calculation \n                                of which object to spawn.\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=0),
                            child_state=TunableStateValueReference()))),
                locked_args={'spawn_type': SLOT_SPAWNER}),
            interaction_spawning=TunableTuple(
                locked_args={'spawn_type': INTERACTION_SPAWNER})),
        'spawn_times':
        OptionalTunable(
            description=
            '\n            Schedule of when the spawners should trigger.\n            If this time is tuned spawners will trigger according to this \n            schedule instead of the spawner commodities.   \n            This should be used for spawners that are on the open neighborhood \n            so that those spawners are time based instead of commodity based.\n            ',
            tunable=TunableWeeklyScheduleFactory(),
            disabled_name='No_custom_spawn_times',
            enabled_name='Set_custom_spawn_times')
    }
    FACTORY_TUNABLES = INSTANCE_TUNABLES

    @flexmethod
    def create_spawned_object(cls,
                              inst,
                              spawner_object,
                              definition,
                              loc_type=ItemLocation.ON_LOT):
        obj = create_object(definition, loc_type=loc_type)
        if obj is not None:
            spawner_object.spawner_component._spawned_objects.add(obj)
        return obj
Example #23
0
class SeasonAwareComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=SEASON_AWARE_COMPONENT):
    FACTORY_TUNABLES = {'seasonal_state_mapping': TunableMapping(description='\n            A tunable mapping linking a season to the state the component\n            owner should have.\n            ', key_type=TunableEnumEntry(description='\n                The season we are interested in.\n                ', tunable_type=SeasonType, default=None), value_type=TunableList(description='\n                A tunable list of states to apply to the owning object of\n                this component when it transitions to this season.\n                ', tunable=TunableStateValueReference(pack_safe=True)))}

    @classproperty
    def required_packs(cls):
        return (Pack.EP05,)

    def on_add(self):
        self.on_season_set(services.season_service().season)

    def on_finalize_load(self):
        self.on_season_set(services.season_service().season)

    def on_season_set(self, season_type):
        if season_type in self.seasonal_state_mapping:
            for state_value in self.seasonal_state_mapping[season_type]:
                self.owner.set_state(state_value.state, state_value)
Example #24
0
class WatchSuperInteraction(SuperInteraction):
    INSTANCE_TUNABLES = {
        'off_channel':
        TunableStateValueReference(
            description=
            '\n            The off channel. The last Sim using the TV will set the object to\n            this state.\n            '
        ),
        'required_channel':
        TunableStateValueReference(
            description='\n            The channel to watch.\n            '),
        'remote_animation':
        TunableAnimationReference(
            description=
            '\n            The animation for using the TV remote.\n            '
        ),
        'sim_view_discourage_area_width':
        Tunable(
            description=
            '\n            The width of the discouragement region placed from a viewing Sim to\n            the TV.\n            ',
            tunable_type=float,
            default=0.4)
    }
    CHANGE_CHANNEL_XEVT_ID = 101

    def _add_route_goal_suppression_region_to_quadtree(self, *args, **kwargs):
        if self.target is None:
            return
        object_point = self.target.location.transform.translation
        sim_point = self.sim.intended_location.transform.translation
        delta = object_point - sim_point
        delta_length = delta.magnitude()
        sim_point_offset = self.sim_view_discourage_area_width * 2
        if delta_length < sim_point_offset:
            return
        start_point = sim_point + delta / (delta_length / sim_point_offset)
        geo = build_rectangle_from_two_points_and_radius(
            object_point, start_point, self.sim_view_discourage_area_width)
        services.sim_quadtree().insert(self.sim, self.id,
                                       placement.ItemType.ROUTE_GOAL_PENALIZER,
                                       geo, self.sim.routing_surface, False, 0)

    def _remove_route_goal_suppression_region_from_quadtree(self):
        services.sim_quadtree().remove(self.id,
                                       placement.ItemType.ROUTE_GOAL_PENALIZER,
                                       0)

    def _refresh_watching_discouragement_stand_region(self, *args, **kwargs):
        self._remove_route_goal_suppression_region_from_quadtree()
        self._add_route_goal_suppression_region_to_quadtree()

    def _start_route_goal_suppression(self, _):
        self.sim.routing_component.on_intended_location_changed.append(
            self._refresh_watching_discouragement_stand_region)
        self._add_route_goal_suppression_region_to_quadtree()

    def _stop_route_goal_suppression(self, _):
        self._remove_route_goal_suppression_region_from_quadtree()
        self.sim.routing_component.on_intended_location_changed.remove(
            self._refresh_watching_discouragement_stand_region)

    def ensure_state(self, desired_channel):
        return conditional_animation(self, desired_channel,
                                     self.CHANGE_CHANNEL_XEVT_ID,
                                     self.affordance.remote_animation)

    def _changed_state_callback(self, target, state, old_value, new_value):
        if new_value is not self.off_channel and new_value.affordance is not None:
            context = self.context.clone_for_continuation(self)
            affordance = self.generate_continuation_affordance(
                new_value.affordance)
            aop = AffordanceObjectPair(affordance, self.target, affordance,
                                       None)
            aop.test_and_execute(context)
        self.cancel(
            FinishingType.OBJECT_CHANGED,
            cancel_reason_msg=
            'state: interaction canceled on state change ({} != {})'.format(
                new_value.value, self.required_channel.value))

    def _run_interaction_gen(self, timeline):
        result = yield from element_utils.run_child(
            timeline,
            build_critical_section_with_finally(
                self._start_route_goal_suppression,
                build_critical_section(
                    build_critical_section(
                        self.ensure_state(self.affordance.required_channel),
                        objects.components.state.with_on_state_changed(
                            self.target,
                            self.affordance.required_channel.state,
                            self._changed_state_callback,
                            super()._run_interaction_gen)),
                    maybe(
                        lambda: len(self.target.get_users(sims_only=True)) ==
                        1, self.ensure_state(self.off_channel))),
                self._stop_route_goal_suppression))
        return result
        yield
Example #25
0
class SpawnerTuning(HasTunableReference,
                    HasTunableSingletonFactory,
                    AutoFactoryInit,
                    metaclass=TunedInstanceMetaclass,
                    manager=services.get_instance_manager(
                        sims4.resources.Types.RECIPE)):
    GROUND_SPAWNER = 1
    SLOT_SPAWNER = 2
    INTERACTION_SPAWNER = 3
    SPAWNER_LOCATION = 0
    PORTAL_LOCATION = 1
    INSTANCE_TUNABLES = {
        'object_reference':
        TunableList(
            description=
            '\n            List of objects the spawner can create.  When the random check \n            picks this value from the weight calculation it will give all\n            the items tuned on this list.\n            ',
            tunable=TunableVariant(
                description=
                '\n                Specify the means by which will the spawner will create the object.\n                ',
                object_definition=ObjectCreator.TunableFactory(
                    get_definition=(True, )),
                recipe=RecipeCreator.TunableFactory(),
                default='object_definition')),
        'spawn_weight':
        TunableRange(
            description=
            '\n            Weight that object will have on the probability calculation \n            of which object to spawn.\n            ',
            tunable_type=int,
            default=1,
            minimum=0),
        'spawn_chance':
        TunablePercent(
            description=
            '\n             The chance that the spawned object will actually be created.\n             This is in case we want spawned objects to not be created in a \n             predictable behavior and the change of "nothing happening" be \n             available.\n             ',
            default=100),
        'spawner_option':
        TunableVariant(
            description=
            '\n            Type of spawners to create:\n            Ground type - Spawned object will appear on the floor at a tunable \n            radius from the spawner object.\n            Slot type - Spawned object will appear on an available slot of \n            a tunable slot type in the spawner object.\n            Interaction type - Spawned objects will appear on the inventory\n            when player makes a gather-harvest-scavenge interaction on them. \n            ',
            ground_spawning=TunableTuple(
                radius=TunableRange(
                    description=
                    '\n                    Max radius at which the spawned object should appear\n                    ',
                    tunable_type=int,
                    default=1,
                    minimum=0),
                min_radius=TunableRange(
                    description=
                    '\n                    Minimum distance away from the portal location to\n                    start looking for a good location.\n                    ',
                    tunable_type=int,
                    default=0,
                    minimum=0),
                restrictions=TunableList(
                    description=
                    '\n                    List of orientation restrictions used by FGL \n                    when searching for a place to put the object.\n                    \n                    Will only apply to off-lot spawners.\n                    ',
                    tunable=TunableOrientationRestriction()),
                placement_scoring=TunableList(
                    description=
                    '\n                    List of scoring functions used by FGL to determine\n                    best places to put the object.\n\n                    Will only apply to off-lot spawners.\n                    ',
                    tunable=TunablePlacementScoringFunction()),
                force_states=TunableList(
                    description=
                    '\n                    List of states the created object will be pushed to.\n                    ',
                    tunable=TunableStateValueReference(pack_safe=True)),
                force_initialization_spawn=OptionalTunable(
                    description=
                    '\n                    If checked, objects with this component will force a \n                    spawning of objects on initialization.  This is mainly used\n                    for objects on the open street where we want to fake that \n                    some time has already passed.  \n                    Additionally, if checked, objects will force the states\n                    on this list instead of the force_states list on the \n                    general spawner tuning, this way we can add some custom\n                    states only for the initialization spawn.\n                    ',
                    tunable=TunableList(
                        description=
                        '\n                        List of states the created object will have when\n                        initialized.\n                        ',
                        tunable=TunableStateValueReference())),
                location_test=TunableTuple(
                    is_outside=OptionalTunable(
                        description=
                        '\n                        If checked, will verify if the spawned object is \n                        located outside. \n                        If unchecked will test the object is not outside\n                        ',
                        disabled_name="Don't_Test",
                        tunable=Tunable(bool, True)),
                    is_natural_ground=OptionalTunable(
                        description=
                        '\n                        If checked, will verify the spawned object is on \n                        natural ground.\n                        If unchecked will test the object is not on natural \n                        ground\n                        ',
                        disabled_name="Don't_Test",
                        tunable=Tunable(bool, True))),
                starting_location=TunableVariant(
                    description=
                    '\n                    The location at which we want to start attempting to place\n                    the object we are creating.\n                    ',
                    spawner_location=TunableTuple(
                        description=
                        '\n                        If selected the object will be spawned near the\n                        location of the spawner object.\n                        ',
                        consider_source_object_footprint=Tunable(
                            description=
                            "\n                            If True, then the source object's footprints will\n                            be considered in the creation of FGL context.\n                            \n                            Example: If the source is invisible, then setting\n                            this to False would allow the spawned object to be\n                            located at its spawner's location. If the source\n                            is a visible object, then setting this to True would\n                            force the spawned object to be offset by any existing\n                            footprints on the source.\n                            ",
                            tunable_type=bool,
                            default=False),
                        locked_args={'location_type': SPAWNER_LOCATION}),
                    portal_location=TunableTuple(
                        description=
                        '\n                        If selected the object will be spanwed near the\n                        location of the specified portal type and start or end\n                        location\n                        ',
                        portal_type=TunableReference(
                            description=
                            '\n                            A reference to the type of portal to use for the\n                            starting location.\n                            ',
                            manager=services.get_instance_manager(
                                sims4.resources.Types.SNIPPET),
                            class_restrictions=('PortalData', )),
                        portal_direction=TunableVariant(
                            description=
                            '\n                            Choose between the There and Back of the portal.\n                            This will not work properly if the portal is\n                            missing a Back and Back is specified here.\n                            ',
                            locked_args={
                                'there':
                                PortalComponent.PORTAL_DIRECTION_THERE,
                                'back': PortalComponent.PORTAL_DIRECTION_BACK
                            },
                            default='there'),
                        portal_location=TunableVariant(
                            description=
                            '\n                            Choose between the entry and exit location of the\n                            portal.\n                            ',
                            locked_args={
                                'entry': PortalComponent.PORTAL_LOCATION_ENTRY,
                                'exit': PortalComponent.PORTAL_LOCATION_EXIT
                            },
                            default='entry'),
                        locked_args={'location_type': PORTAL_LOCATION}),
                    default='spawner_location'),
                locked_args={'spawn_type': GROUND_SPAWNER}),
            slot_spawning=TunableTuple(
                slot_type=TunableReference(
                    description=
                    '\n                    Slot type where spawned objects should appear\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        SLOT_TYPE)),
                force_initialization_spawn=OptionalTunable(
                    description=
                    '\n                    If enabled, objects with this component will force a \n                    spawning of objects on initialization.  This is mainly used\n                    for objects on the open street where we want to fake that \n                    some time has already passed.\n                    ',
                    tunable=TunableRange(
                        description=
                        '\n                        The number of objects to be created.\n                        ',
                        tunable_type=int,
                        minimum=1,
                        default=1)),
                state_mapping=TunableMapping(
                    description=
                    '\n                    Mapping of states from the spawner object into the possible\n                    states that the spawned object may have\n                    ',
                    key_type=TunableStateValueReference(),
                    value_type=TunableList(
                        description=
                        '\n                        List of possible children for a parent state\n                        ',
                        tunable=TunableTuple(
                            description=
                            '\n                            Pair of weight and possible state that the spawned \n                            object may have\n                            ',
                            weight=TunableRange(
                                description=
                                '\n                                Weight that object will have on the probability calculation \n                                of which object to spawn.\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=0),
                            child_state=TunableStateValueReference()))),
                locked_args={'spawn_type': SLOT_SPAWNER}),
            interaction_spawning=TunableTuple(
                locked_args={'spawn_type': INTERACTION_SPAWNER})),
        'spawn_times':
        OptionalTunable(
            description=
            '\n            Schedule of when the spawners should trigger.\n            If this time is tuned spawners will trigger according to this \n            schedule instead of the spawner commodities.   \n            This should be used for spawners that are on the open neighborhood \n            so that those spawners are time based instead of commodity based.\n            ',
            tunable=WeeklySchedule.TunableFactory(),
            disabled_name='No_custom_spawn_times',
            enabled_name='Set_custom_spawn_times')
    }
    FACTORY_TUNABLES = INSTANCE_TUNABLES

    @flexmethod
    def create_spawned_object(cls,
                              inst,
                              spawner_object,
                              definition,
                              loc_type=ItemLocation.ON_LOT):
        try:
            obj = definition(loc_type=loc_type)
        except KeyError:
            logger.exception('Failed to spawn object {} for {}', definition,
                             spawner_object)
            obj = None
        if obj is not None:
            spawner_object.spawner_component.spawned_object_created(obj)
        return obj
Example #26
0
class TimeOfDayComponent(Component,
                         HasTunableFactory,
                         component_name=types.TIME_OF_DAY_COMPONENT):
    DAILY_REPEAT = date_and_time.create_time_span(hours=24)
    FACTORY_TUNABLES = {
        'state_changes':
        TunableMapping(
            description=
            '\n            A mapping from state to times of the day when the state should be \n            set to a tuned value.\n            ',
            key_type=TunableStateTypeReference(
                description=
                '\n                The state to be set.\n                '),
            value_type=TunableList(
                description=
                '\n                List of times to modify the state at.\n                ',
                tunable=TunableTuple(
                    start_time=TunableRange(
                        description=
                        '\n                        The start time (24 hour clock time) for the Day_Time state.\n                        ',
                        tunable_type=float,
                        default=0,
                        minimum=0,
                        maximum=24),
                    value=TunableStateValueReference(
                        description=
                        '\n                        New state value.\n                        '
                    ),
                    loot_list=TunableList(
                        description=
                        '\n                        A list of loot operations to apply when changing state.\n                        ',
                        tunable=TunableReference(
                            manager=services.get_instance_manager(
                                sims4.resources.Types.ACTION),
                            class_restrictions=('LootActions', ),
                            pack_safe=True)),
                    chance=SuccessChance.TunableFactory(
                        description=
                        '\n                        Percent chance that the state change will be considered. \n                        The chance is evaluated just before running the tests.\n                        '
                    ),
                    tests=TunableTestSet(
                        description=
                        '\n                        Test to decide whether the state change can be applied.\n                        '
                    ))))
    }

    def __init__(self, owner, *, state_changes):
        super().__init__(owner)
        self.state_changes = state_changes
        self.alarm_handles = []

    def _apply_state_change(self, state, change):
        resolver = SingleObjectResolver(self.owner)
        chance = change.chance.get_chance(resolver)
        if random.random() > chance:
            return
        if not change.tests.run_tests(resolver):
            return
        self.owner.set_state(state, change.value)
        for loot_action in change.loot_list:
            loot_action.apply_to_resolver(resolver)

    def _add_alarm(self, cur_state, state, change):
        now = services.time_service().sim_now
        time_to_day = clock.time_until_hour_of_day(now, change.start_time)

        def alarm_callback(_):
            self._apply_state_change(state, change)

        self.alarm_handles.append(
            alarms.add_alarm(self.owner,
                             time_to_day,
                             alarm_callback,
                             repeating=True,
                             repeating_time_span=self.DAILY_REPEAT))
        if cur_state is None or time_to_day > cur_state[0]:
            return (time_to_day, change)
        return cur_state

    def on_add(self):
        for (state, changes) in self.state_changes.items():
            current_state = None
            for change in changes:
                current_state = self._add_alarm(current_state, state, change)
            if current_state is not None:
                self._apply_state_change(state, current_state[1])

    def on_remove(self):
        for handle in self.alarm_handles:
            alarms.cancel_alarm(handle)
Example #27
0
class CurfewComponent(Component,
                      HasTunableFactory,
                      AutoFactoryInit,
                      component_name=CURFEW_COMPONENT):
    FACTORY_TUNABLES = {
        'curfew_state_reference':
        TunableStateTypeReference(
            description=
            '\n            This is a reference to the State type we will be manipulating when\n            we change states on this object.\n            '
        ),
        'times_state_reference':
        TunableStateTypeReference(
            description=
            '\n            This is a reference to the State type we will be manipulating when\n            we change states on this object.\n            '
        ),
        'curfew_not_set':
        TunableStateValueReference(
            description=
            '\n            This is the reference to the state to apply on the owning object\n            when there is no active curfew setting. Or the setting is UNSET.\n            '
        ),
        'curfew_warning_state':
        TunableStateValueReference(
            description=
            '\n            This is the reference to the state to apply to the owning object\n            when the curfew is about to start.\n            '
        ),
        'curfew_past_state':
        TunableStateValueReference(
            description=
            '\n            This is the reference to the state to apply to the owning object\n            when curfew is active.\n            '
        ),
        'curfew_on_state':
        TunableStateValueReference(
            description=
            '\n            This is the reference to the state to apply to the owning object\n            when the curfew is set but not currently active.\n            '
        ),
        'not_set_state':
        TunableStateValueReference(
            description=
            "\n            This is the reference to the state to apply to the owning object\n            when there isn't a curfew set at all.\n            "
        ),
        'times_set':
        TunableMapping(
            description=
            '\n            This is a Mapping of time (in military time) to state to apply to\n            the owning object in order to display the correct time that the\n            curfew is set for.\n            ',
            key_type=int,
            value_type=TunableStateValueReference()),
        'set_curfew_affordances':
        TunableList(
            description=
            '\n            A List of the interactions that will be used to set the curfew\n            via this object.\n            ',
            tunable=TunableReference(
                description=
                '\n              This is the interaction that will be used to "set" the curfew.\n                ',
                manager=services.affordance_manager(),
                class_restrictions=('SuperInteraction', )))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._current_curfew_setting = None

    def on_add(self):
        curfew_service = services.get_curfew_service()
        current_curfew = curfew_service.get_zone_curfew(
            services.current_zone_id())
        if current_curfew not in self.times_set:
            pass
        self.apply_state_for_setting(current_curfew)
        self.apply_warning_state(current_curfew)
        self._register_for_alarms(curfew_service)

    def on_remove(self):
        curfew_service = services.get_curfew_service()
        self._unregister_for_alarms(curfew_service)

    def component_super_affordances_gen(self, **kwargs):
        yield from self.set_curfew_affordances

    def update_states(self, curfew_setting):
        if curfew_setting == self._current_curfew_setting:
            return
        self.apply_state_for_setting(curfew_setting)
        self.apply_warning_state(curfew_setting)

    def apply_state_for_setting(self, setting):
        if setting is CurfewService.UNSET:
            self.owner.set_state(self.times_state_reference,
                                 self.not_set_state)
        state_to_apply = self.times_set.get(setting)
        if state_to_apply is not None:
            self.owner.set_state(self.times_state_reference, state_to_apply)

    def apply_warning_state(self, curfew_setting):
        if curfew_setting is CurfewService.UNSET:
            self.owner.set_state(self.curfew_state_reference,
                                 self.curfew_not_set)
            return
        now = services.time_service().sim_now.hour()
        if now >= CurfewService.CURFEW_END_TIME and now < curfew_setting:
            self._on_curfew_over_alarm()
        elif now == curfew_setting - 1:
            self._on_warning_time_alarm()
        else:
            self._on_curfew_started_alarm()

    def _register_for_alarms(self, curfew_service):
        curfew_service.register_for_alarm_callbacks(
            self._on_warning_time_alarm, self._on_curfew_started_alarm,
            self._on_curfew_over_alarm, self.update_states)

    def _unregister_for_alarms(self, curfew_service):
        curfew_service.unregister_for_alarm_callbacks(
            self._on_warning_time_alarm, self._on_curfew_started_alarm,
            self._on_curfew_over_alarm, self.update_states)

    def _on_warning_time_alarm(self):
        self.owner.set_state(self.curfew_state_reference,
                             self.curfew_warning_state,
                             force_update=True)

    def _on_curfew_started_alarm(self):
        self.owner.set_state(self.curfew_state_reference,
                             self.curfew_past_state,
                             force_update=True)

    def _on_curfew_over_alarm(self):
        self.owner.set_state(self.curfew_state_reference,
                             self.curfew_on_state,
                             force_update=True)
class CreateCarriedObjectSuperInteraction(SuperInteraction):
    __qualname__ = 'CreateCarriedObjectSuperInteraction'
    INSTANCE_TUNABLES = {
        'definition':
        TunableReference(
            description='\n            The object to create.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            manager=services.definition_manager()),
        'carry_track_override':
        OptionalTunable(
            description=
            '\n            If enabled, specify which carry track the Sim must use to carry the\n            created object.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableEnumEntry(
                description=
                '\n                Which hand to carry the object in.\n                ',
                tunable_type=PostureTrack,
                default=PostureTrack.RIGHT)),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the finished object as soon as it is\n            created.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableStateValueReference()),
        'continuation':
        SuperInteraction.TunableReference(
            description=
            '\n            An interaction to push as a continuation to the carry.\n            '
        ),
        'continuation_with_affordance_overrides':
        OptionalTunable(
            description=
            "\n            If enabled, allows you to specify a continuation to the\n            carry based on a participant's object definition.\n            This continuation will be pushed in addition to the tunable continuation,\n            although you will rarely need to tune both at the same time.\n            ",
            tunable=TunableTuple(
                continuation=TunableContinuation(
                    description=
                    '\n                    A tunable continuation to push based on the parameters provided.\n                    '
                ),
                participant=TunableEnumEntry(
                    description=
                    '\n                    When using the affordance_override mapping, this\n                    is the participant we will use to get the definition.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.PickedObject),
                affordance_override=TunableMapping(
                    description=
                    "\n                    Based on the participants's object definition, you can override\n                    the affordance on the tunable continuation.\n                    ",
                    key_type=TunableReference(
                        description=
                        '\n                        The object definition to look for.\n                        ',
                        manager=services.definition_manager()),
                    value_type=SuperInteraction.TunableReference())),
            tuning_group=GroupNames.CREATE_CARRYABLE)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._object_create_helper = None

    @property
    def create_target(self):
        return self.definition

    @property
    def created_target(self):
        if self._object_create_helper is not None:
            return self._object_create_helper.object

    def _get_create_continuation_affordance(self):
        def create_continuation_affordance():
            context = self.context.clone_for_continuation(self)
            aop = AffordanceObjectPair(self.continuation, self.created_target,
                                       self.continuation, None)
            return (aop, context)

        if self.continuation is not None:
            return create_continuation_affordance

    def build_basic_content(self, sequence, **kwargs):
        super_build_basic_content = super().build_basic_content

        def setup_object(obj):
            for initial_state in reversed(self.initial_states):
                obj.set_state(initial_state.state, initial_state)
            obj.set_household_owner_id(self.sim.household.id)

        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self.definition,
            self,
            init=setup_object,
            tag='CreateCarriedObjectSuperInteraction')

        def claim_object(*_, **__):
            self._object_create_helper.claim()

        def set_carry_target(_):
            if self.carry_track_override:
                self.track = self.carry_track_override
            else:
                self.track = DEFAULT
            if self.track is None:
                return False
            self.context.carry_target = self.created_target

        def push_tunable_continuation_with_affordance_overrides(_):
            if self.continuation_with_affordance_overrides is None:
                return
            obj = self.get_participant(
                self.continuation_with_affordance_overrides.participant)
            if obj is not None:
                affordance_override = self.continuation_with_affordance_overrides.affordance_override.get(
                    obj.definition)
            else:
                affordance_override = None
            interaction_parameters = {}
            if 'picked_item_ids' in self.interaction_parameters:
                interaction_parameters[
                    'picked_item_ids'] = self.interaction_parameters[
                        'picked_item_ids']
            self.push_tunable_continuation(
                self.continuation_with_affordance_overrides.continuation,
                affordance_override=affordance_override,
                **interaction_parameters)

        def enter_carry(timeline):
            result = yield element_utils.run_child(
                timeline,
                enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=claim_object,
                    create_si_fn=self._get_create_continuation_affordance(),
                    track=self.track,
                    sequence=build_critical_section(
                        super_build_basic_content(sequence, **kwargs),
                        flush_all_animations)))
            return result

        return (self._object_create_helper.create(
            set_carry_target, enter_carry,
            push_tunable_continuation_with_affordance_overrides),
                lambda _: self._object_create_helper.claimed)
class LoudNeighborSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {'loud_neighbor_state': _LoudNeighborState.TunableFactory(description='\n            The situation state used for when a neighbor starts being loud.\n            This will listen for a Sim to bang on the door and complain about\n            the noise before transitioning to the complain state.\n            ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='01_loud_neighbor_situation_state'), 'complain_state': _ComplainState.TunableFactory(description="\n            The situation state used for when a player Sim has banged on the\n            neighbor's door and we are waiting for them to complain to the\n            neighbor.\n            ", tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='02_complain_situation_state'), 'loud_neighbor_filters': TunableList(description='\n            Sim filters that fit the description of loud neighbor(s). We run\n            through them until we find someone matching the filter.\n            ', tunable=TunableVariant(description='\n                The filter we want to use to find loud neighbors.\n                ', single_filter=TunableSimFilter.TunablePackSafeReference(description='\n                    A Sim Filter to find a loud neighbor.\n                    '), aggregate_filter=TunableSimFilter.TunablePackSafeReference(description='\n                    An aggregate Sim Filter to find a loud neighbor that has\n                    other Sims.\n                    ', class_restrictions=filters.tunable.TunableAggregateFilter), default='single_filter')), 'loud_door_state_on': TunableStateValueReference(description='\n            State to set on the apartment door of the loud neighbor when the\n            situation starts.\n            '), 'loud_door_state_off': TunableStateValueReference(description='\n            State to set on the apartment door of the loud neighbor when they\n            are no longer being loud.\n            ')}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        reader = self._seed.custom_init_params_reader
        if reader is not None:
            self._neighbor_sim_id = reader.read_uint64(NEIGHBOR_TOKEN, None)
            self._neighbor_door_id = reader.read_uint64(DOOR_TOKEN, None)
        else:
            self._neighbor_sim_id = None
            self._neighbor_door_id = None

    @classproperty
    def allow_user_facing_goals(cls):
        return False

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return []

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _LoudNeighborState, factory=cls.loud_neighbor_state), SituationStateData(2, _ComplainState, factory=cls.complain_state))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def situation_meets_starting_requirements(cls, **kwargs):
        neighbor_sim_id = cls._get_loud_neighbor()
        if neighbor_sim_id is None:
            return False
        return True

    def start_situation(self):
        super().start_situation()
        neighbor_sim_id = self._get_loud_neighbor()
        self._set_loud_neighbor_and_door(neighbor_sim_id)
        if self._neighbor_sim_id is not None:
            self._change_state(self.loud_neighbor_state())

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        if self._neighbor_sim_id is not None:
            writer.write_uint64(NEIGHBOR_TOKEN, self._neighbor_sim_id)
        if self._neighbor_door_id is not None:
            writer.write_uint64(DOOR_TOKEN, self._neighbor_door_id)

    def _destroy(self):
        if self._neighbor_door_id is not None:
            apartment_door = services.object_manager().get(self._neighbor_door_id)
            if apartment_door is not None:
                apartment_door.set_state(self.loud_door_state_off.state, self.loud_door_state_off)
        services.get_zone_situation_manager().remove_sim_from_auto_fill_blacklist(self._neighbor_sim_id)
        super()._destroy()

    @classmethod
    def _get_loud_neighbor(cls):
        neighbor_sim_id = None
        blacklist_sim_ids = {sim_info.sim_id for sim_info in services.active_household()}
        blacklist_sim_ids.update(set(sim_info.sim_id for sim_info in services.sim_info_manager().instanced_sims_gen()))
        loud_neighbor_filters = sorted(cls.loud_neighbor_filters, key=lambda *args: random.random())
        for neighbor_filter in loud_neighbor_filters:
            neighbors = services.sim_filter_service().submit_matching_filter(sim_filter=neighbor_filter, allow_yielding=False, blacklist_sim_ids=blacklist_sim_ids, gsi_source_fn=cls.get_sim_filter_gsi_name)
            neighbor_sim_infos_at_home = [result.sim_info for result in neighbors if result.sim_info.is_at_home]
            if len(neighbor_sim_infos_at_home) > 1 or len(neighbor_sim_infos_at_home):
                if neighbor_filter.is_aggregate_filter():
                    continue
                neighbor_sim_id = neighbor_sim_infos_at_home[0].sim_id if neighbor_sim_infos_at_home else None
                if neighbor_sim_id is not None:
                    break
        return neighbor_sim_id

    def _set_loud_neighbor_and_door(self, neighbor_sim_id):
        neighbor_sim_info = services.sim_info_manager().get(neighbor_sim_id)
        if neighbor_sim_info is None:
            self._self_destruct()
            return
        self._neighbor_sim_id = neighbor_sim_id
        door_service = services.get_door_service()
        plex_door_infos = door_service.get_plex_door_infos()
        object_manager = services.object_manager()
        for door_info in plex_door_infos:
            door = object_manager.get(door_info.door_id)
            if door is not None:
                if door.household_owner_id == neighbor_sim_info.household_id:
                    self._neighbor_door_id = door_info.door_id
                    break
        else:
            logger.error('Could not find door object that belongs to {}', neighbor_sim_info.household.name)
            self._self_destruct()
class ObjectRelationshipComponent(Component, HasTunableFactory, component_name=types.OBJECT_RELATIONSHIP_COMPONENT, persistence_key=protocols.PersistenceMaster.PersistableData.ObjectRelationshipComponent):
    __qualname__ = 'ObjectRelationshipComponent'
    FACTORY_TUNABLES = {'number_of_allowed_relationships': OptionalTunable(description='\n            Number of Sims who can have a relationship with this object at one\n            time.  If not specified, an infinite number of Sims can have a \n            relationship with the object.\n            ', tunable=TunableRange(tunable_type=int, default=1, minimum=1)), 'relationship_stat': Statistic.TunableReference(description="\n            The statistic which will be created for each of this object's\n            relationships.\n            "), 'relationship_track_visual': OptionalTunable(RelationshipTrack.TunableReference(description='\n                The relationship that this track will visually try and imitate in\n                regards to static track tack data.  If this is None then this\n                relationship will not be sent down to the client.\n                ')), 'relationship_based_state_change_tuning': OptionalTunable(TunableTuple(description='\n            A list of value ranges and associated states.  If the active Sim\n            has a relationship with this object  that falls within one of the\n            value ranges specified here, the object will change state to match\n            the specified state.\n            \n            These state changes exist on a per Sim basis, so this tuning will\n            effectively make the same object appear different depending on\n            which Sim is currently active.\n            ', state_changes=TunableList(tunable=TunableTuple(value_threshold=TunableThreshold(description="\n                        The range that the active Sim's relationship with this\n                        object must fall within in order for this state change to\n                        take place.\n                        "), state=TunableStateValueReference(description="\n                        The state this object will change to if it's relationship\n                        with the active Sim falls within the specified range.\n                        "))), default_state=TunableStateValueReference(description='\n                The state this object will change to if there is no other tuned\n                relationship based state change for the currently active Sim.\n                ')))}

    def __init__(self, owner, number_of_allowed_relationships, relationship_stat, relationship_track_visual, relationship_based_state_change_tuning):
        super().__init__(owner)
        self._number_of_allowed_relationships = number_of_allowed_relationships
        self._relationship_stat = relationship_stat
        self._relationship_track_visual = relationship_track_visual
        self._relationship_based_state_change_tuning = relationship_based_state_change_tuning
        self._state_changes = None
        self._default_state = None
        self._relationships = {}
        self._relationship_changed_callbacks = defaultdict(CallableList)

    def _on_active_sim_change(self, _, new_sim):
        if new_sim is None:
            return
        relationship = self._get_relationship(new_sim.id)
        self._update_state(relationship)

    def _update_state(self, relationship):
        if self._default_state is None:
            return
        if relationship is None:
            new_state = self._default_state
        elif self._state_changes is None:
            new_state = self._default_state
        else:
            for state_change in self._state_changes:
                while state_change.value_threshold.compare(relationship.get_value()):
                    new_state = state_change.state
                    break
            new_state = self._default_state
        self.owner.set_state(new_state.state, new_state)

    @property
    def _can_add_new_relationship(self):
        if self._number_of_allowed_relationships is not None and len(self._relationships) >= self._number_of_allowed_relationships:
            return False
        return True

    def on_add(self):
        if self._relationship_based_state_change_tuning is None:
            return
        self._state_changes = self._relationship_based_state_change_tuning.state_changes
        self._default_state = self._relationship_based_state_change_tuning.default_state
        services.current_zone().register_callback(zone_types.ZoneState.CLIENT_CONNECTED, self._register_active_sim_change)
        services.current_zone().register_callback(zone_types.ZoneState.HOUSEHOLDS_AND_SIM_INFOS_LOADED, self._publish_relationship_data)

    def on_remove(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.unregister_active_sim_changed(self._on_active_sim_change)

    def _register_active_sim_change(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.register_active_sim_changed(self._on_active_sim_change)

    def _publish_relationship_data(self):
        if not self._relationship_track_visual:
            return
        for sim_id in self._relationships.keys():
            self._send_relationship_data(sim_id)

    def add_relationship_changed_callback_for_sim_id(self, sim_id, callback):
        self._relationship_changed_callbacks[sim_id].append(callback)

    def remove_relationship_changed_callback_for_sim_id(self, sim_id, callback):
        if sim_id in self._relationship_changed_callbacks and callback in self._relationship_changed_callbacks[sim_id]:
            self._relationship_changed_callbacks[sim_id].remove(callback)

    def _trigger_relationship_changed_callbacks_for_sim_id(self, sim_id):
        callbacks = self._relationship_changed_callbacks[sim_id]
        if callbacks is not None:
            callbacks()

    def add_relationship(self, sim_id):
        if sim_id in self._relationships:
            return False
        if not self._can_add_new_relationship:
            return False
        stat = self._relationship_stat(None)
        self._relationships[sim_id] = stat
        stat.on_add()
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        return True

    def remove_relationship(self, sim_id):
        if sim_id not in self._relationships:
            return
        del self._relationships[sim_id]
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)

    def modify_relationship(self, sim_id, value, add=True):
        if not add:
            return
        if not (sim_id not in self._relationships and self.add_relationship(sim_id)):
            return
        self._relationships[sim_id].add_value(value)
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        client = services.client_manager().get_first_client()
        if client is not None and client.active_sim is not None and client.active_sim.sim_id == sim_id:
            self._update_state(self._relationships[sim_id])

    def _get_relationship(self, sim_id):
        return self._relationships.get(sim_id)

    def has_relationship(self, sim_id):
        return sim_id in self._relationships

    def get_relationship_value(self, sim_id):
        relationship = self._get_relationship(sim_id)
        if relationship is not None:
            return relationship.get_value()
        return self._relationship_stat.initial_value

    def get_relationship_initial_value(self):
        return self._relationship_stat.initial_value

    def _send_relationship_data(self, sim_id):
        if self._relationship_track_visual is None:
            return
        relationship_to_send = self._get_relationship(sim_id)
        if not relationship_to_send:
            return
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return
        msg = commodity_protocol.RelationshipUpdate()
        msg.actor_sim_id = sim_id
        (msg.target_id.object_id, msg.target_id.manager_id) = self.owner.icon_info
        msg.target_instance_id = self.owner.id
        with ProtocolBufferRollback(msg.tracks) as relationship_track_update:
            relationship_value = relationship_to_send.get_value()
            relationship_track_update.track_score = relationship_value
            relationship_track_update.track_bit_id = self._relationship_track_visual.get_bit_at_relationship_value(relationship_value).guid64
            relationship_track_update.track_id = self._relationship_track_visual.guid64
            relationship_track_update.track_popup_priority = self._relationship_track_visual.display_popup_priority
        send_relationship_op(sim_info, msg)

    def save(self, persistence_master_message):
        if not self._relationships:
            return
        persistable_data = protocols.PersistenceMaster.PersistableData()
        persistable_data.type = protocols.PersistenceMaster.PersistableData.ObjectRelationshipComponent
        relationship_component_data = persistable_data.Extensions[protocols.PersistableObjectRelationshipComponent.persistable_data]
        for (key, value) in self._relationships.items():
            with ProtocolBufferRollback(relationship_component_data.relationships) as relationship_data:
                relationship_data.sim_id = key
                relationship_data.value = value.get_value()
        persistence_master_message.data.extend([persistable_data])

    def load(self, persistable_data):
        relationship_component_data = persistable_data.Extensions[protocols.PersistableObjectRelationshipComponent.persistable_data]
        for relationship in relationship_component_data.relationships:
            self.modify_relationship(relationship.sim_id, relationship.value)