コード例 #1
0
class XevtTriggeredElement(elements.ParentElement, HasTunableFactory,
                           AutoFactoryInit):
    AT_BEGINNING = 'at_beginning'
    AT_END = 'at_end'
    ON_XEVT = 'on_xevt'
    TIMING_DESCRIPTION = '\n        Determines the exact timing of the behavior, either at the beginning\n        of an interaction, the end, or when an xevt occurs in an animation\n        played as part of the interaction.\n        '
    FakeTiming = collections.namedtuple(
        'FakeTiming', ('timing', 'offset_time', 'criticality', 'xevt_id'))
    LOCKED_AT_BEGINNING = FakeTiming(AT_BEGINNING, None, None, None)
    LOCKED_AT_END = FakeTiming(AT_END, None, None, None)
    LOCKED_ON_XEVT = FakeTiming(ON_XEVT, None, None, None)
    FACTORY_TUNABLES = {
        'timing':
        TunableVariant(
            description=TIMING_DESCRIPTION,
            default=AT_END,
            at_beginning=TunableTuple(
                description=
                "\n                The behavior should occur at the very beginning of the\n                interaction.  It will not be tightly synchronized visually with\n                animation.  This isn't a very common use case and would most\n                likely be used in an immediate interaction or to change hidden\n                state that is used for bookkeeping rather than visual\n                appearance.\n                ",
                offset_time=OptionalTunable(
                    description=
                    '\n                    If enabled, the interaction will wait this amount of time\n                    after the beginning before running the element.\n                    \n                    Only use this if absolutely necessary. Better alternatives\n                    include using xevts, time based conditional action with\n                    loot ops, and using outcomes.\n                    ',
                    tunable=TunableSimMinute(
                        description=
                        'The interaction will wait this amount of time after the beginning before running the element',
                        default=2),
                    deprecated=True),
                locked_args={
                    'timing': AT_BEGINNING,
                    'criticality': CleanupType.NotCritical,
                    'xevt_id': None
                }),
            at_end=TunableTuple(
                description=
                '\n                The behavior should occur at the end of the interaction.  It\n                will not be tightly synchronized visually with animation.  An\n                example might be an object that gets dirty every time a Sim uses\n                it (so using a commodity change is overkill) but no precise\n                synchronization with animation is desired, as might be the case\n                with vomiting in the toilet.\n                ',
                locked_args={
                    'timing': AT_END,
                    'xevt_id': None,
                    'offset_time': None
                },
                criticality=TunableEnumEntry(CleanupType,
                                             CleanupType.OnCancel)),
            on_xevt=TunableTuple(
                description=
                "\n                The behavior should occur synchronized visually with an xevt in\n                an animation played as part of the interaction.  If for some\n                reason such an event doesn't occur, the behavior will occur at\n                the end of the interaction.  This is by far the most common use\n                case, as when a Sim flushes a toilet and the water level should\n                change when the actual flush animation and effects fire.\n                ",
                locked_args={
                    'timing': ON_XEVT,
                    'offset_time': None
                },
                criticality=TunableEnumEntry(CleanupType,
                                             CleanupType.OnCancel),
                xevt_id=Tunable(int, 100))),
        'success_chance':
        SuccessChance.TunableFactory(
            description=
            '\n            The percentage chance that this action will be applied.\n            '
        )
    }

    def __init__(self, interaction, *, timing, sequence=(), **kwargs):
        super().__init__(timing=None, **kwargs)
        self.interaction = interaction
        self.sequence = sequence
        self.timing = timing.timing
        self.criticality = timing.criticality
        self.xevt_id = timing.xevt_id
        self.result = None
        self.triggered = False
        self.offset_time = timing.offset_time
        self.__event_handler_handle = None
        success_chance = self.success_chance.get_chance(
            interaction.get_resolver())
        self._should_do_behavior = random.random() <= success_chance

    def _register_event_handler(self, element):
        self.__event_handler_handle = self.interaction.animation_context.register_event_handler(
            self._behavior_event_handler, handler_id=self.xevt_id)

    def _release_event_handler(self, element):
        self.__event_handler_handle.release()
        self.__event_handler_handle = None

    def _behavior_element(self, timeline):
        if not self.triggered:
            self.triggered = True
            if self._should_do_behavior:
                self.result = self._do_behavior()
            else:
                self.result = None
        return self.result

    def _behavior_event_handler(self, *_, **__):
        if not self.triggered:
            self.triggered = True
            if self._should_do_behavior:
                self.result = self._do_behavior()
            else:
                self.result = None

    def _run(self, timeline):
        if not self._should_do_behavior:
            return timeline.run_child(build_element(self.sequence))
        if self.timing == self.AT_BEGINNING:
            if self.offset_time is None:
                sequence = [self._behavior_element, self.sequence]
            else:
                sequence = build_delayed_element(self.sequence,
                                                 clock.interval_in_sim_minutes(
                                                     self.offset_time),
                                                 self._behavior_element,
                                                 soft_sleep=True)
        elif self.timing == self.AT_END:
            sequence = [self.sequence, self._behavior_element]
        elif self.timing == self.ON_XEVT:
            sequence = [
                build_critical_section(self._register_event_handler,
                                       self.sequence,
                                       self._release_event_handler),
                self._behavior_element
            ]
        child_element = build_element(sequence, critical=self.criticality)
        child_element = self._build_outer_elements(child_element)
        return timeline.run_child(child_element)

    def _build_outer_elements(self, sequence):
        return sequence

    def _do_behavior(self):
        raise NotImplementedError

    @classmethod
    def validate_tuning_interaction(cls, interaction, basic_extra):
        if basic_extra._tuned_values.timing.timing != XevtTriggeredElement.ON_XEVT:
            return
        if interaction.one_shot and interaction.basic_content.animation_ref is None:
            logger.error(
                'The interaction ({}) has a tuned basic extra ({}) that occurs on an xevt but has no animation content.',
                interaction,
                basic_extra.factory,
                owner='shipark')
        elif interaction.staging:
            staging_content = interaction.basic_content.content.content_set._tuned_values
            if staging_content.affordance_links is None and staging_content.phase_tuning is None and interaction.basic_content.animation_ref is None:
                if interaction.provided_posture_type is None:
                    logger.error(
                        'The interaction ({}) has a tuned basic extra ({}) that occurs on an xevt tuned on a staging interaction without any staging content.',
                        interaction,
                        basic_extra.factory,
                        owner='shipark')
                elif interaction.provided_posture_type._animation_data is None:
                    logger.error(
                        'The posture-providing interaction ({}) has a tuned basic extra ({}) that occurs on an xevt but has no animation content in the posture.',
                        interaction,
                        basic_extra.factory,
                        owner='shipark')
        elif interaction.looping and interaction.basic_content.animation_ref is None:
            logger.error(
                'The interaction ({}) has a tuned basic extra ({}) that occurs on an xevt but has no animation content.',
                interaction,
                basic_extra.factory,
                owner='shipark')

    @classmethod
    def validate_tuning_outcome(cls, outcome, basic_extra, interaction_name):
        if outcome.animation_ref is None and outcome.response is None and outcome.social_animation is None:
            logger.error(
                'The interaction ({}) has an outcome with a tuned basic extra ({}) that occurs on an xevt, but has no animation content.',
                interaction_name,
                basic_extra,
                owner='shipark')
コード例 #2
0
ファイル: scheduler.py プロジェクト: NeonOcean/Environment
 def schedule_entry_data(pack_safe=False, affected_object_cap=False):
     schedule_entry_tuning = {'tuning_name': 'weighted_situations', 'tuning_type': TunableList(description='\n                A weighted list of situations to be used at the scheduled time.\n                ', tunable=TunableTuple(situation=TunableReference(description='\n                        The situation to start according to the tuned schedule.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.SITUATION), pack_safe=pack_safe), params=TunableTuple(description='\n                        Situation creation parameters.\n                        ', user_facing=Tunable(description="\n                            If enabled, we will start the situation as user facing.\n                            Note: We can only have one user facing situation at a time,\n                            so make sure you aren't tuning multiple user facing\n                            situations to occur at once.\n                            ", tunable_type=bool, default=False)), weight_multipliers=TunableMultiplier.TunableFactory(description="\n                        Tunable tested multiplier to apply to weight.\n                        \n                        *IMPORTANT* The only participants that work are ones\n                        available globally, such as Lot and ActiveHousehold. Only\n                        use these participant types or use tests that don't rely\n                        on any, such as testing all objects via Object Criteria\n                        test or testing active zone with the Zone test.\n                        "), tests=TunableTestSet(description="\n                        A set of tests that must pass for the situation and weight\n                        pair to be available for selection.\n                        \n                        *IMPORTANT* The only participants that work are ones\n                        available globally, such as Lot and ActiveHousehold. Only\n                        use these participant types or use tests that don't rely\n                        on any, such as testing all objects via Object Criteria\n                        test or testing active zone with the Zone test.\n                        ")))}
     if affected_object_cap:
         schedule_entry_tuning['additional_tuning_name'] = 'affected_object_cap'
         schedule_entry_tuning['additional_tuning_type'] = TunableRange(description='\n                Specify the maximum number of objects on the zone lot that \n                can schedule the situations.\n                ', tunable_type=int, minimum=1, default=1)
     return {'schedule_entries': TunableList(description='\n                A list of event schedules. Each event is a mapping of days of the\n                week to a start_time and duration.\n                ', tunable=ScheduleEntry.TunableFactory(schedule_entry_data=schedule_entry_tuning))}
コード例 #3
0
class ThrowingGroupCostFunction(HasTunableFactory, AutoFactoryInit, CostFunctionBase):
    FACTORY_TUNABLES = {'maximum_distance': TunableRange(description='\n            Any distance to another Sim over this amount will be penalized.\n            ', tunable_type=float, default=10.0, minimum=0), 'minimum_distance': TunableRange(description='\n            Any distance to another Sim under this amount will be penalized.\n            ', tunable_type=float, default=3.0, minimum=0), 'adjustment_distance': TunableRange(description='\n            Any position that requires the Sim to be at a distance less than\n            this value will be penalized.\n            ', tunable_type=float, default=5.0, minimum=0), 'location_tests': TunableTuple(description='\n            Tests to run on the goal location to validate if it should be\n            discouraged when using this social group.\n            ', validate_snowmask=OptionalTunable(description='\n                If enabled goals that do not match the snowmask value will\n                be discouraged.  This is used for winter to guarantee cases\n                like snowball fight the Sims readjust and move around in places\n                where there is snow.\n                ', tunable=Tunable(description='\n                    Value snowmask should be greater than to pass this test.\n                    ', tunable_type=float, default=0.5)), validate_is_outside=OptionalTunable(description='\n                If enabled goals that do not match the outside condition will\n                be discouraged.\n                ', tunable=Tunable(description='\n                    If True goals outside will be encouraged, if false only\n                    goals on the inside will be encouraged.\n                    ', tunable_type=bool, default=False)))}
    INVALID_GOAL_SCORE = 20

    def __init__(self, sim, target, force_readjust, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sim = sim
        self._target_ref = target.ref()
        self._force_readjust = force_readjust

    def _score_location(self, position):
        if self.location_tests.validate_snowmask is not None and terrain.get_snowmask_value(position) > self.location_tests.validate_snowmask:
            return ThrowingGroupCostFunction.INVALID_GOAL_SCORE
        elif self.location_tests.validate_is_outside is not None and self.location_tests.validate_is_outside != is_location_outside(position, self._sim.level):
            return ThrowingGroupCostFunction.INVALID_GOAL_SCORE
        return 0.0

    def constraint_cost(self, position, orientation, routing_surface):
        target = self._target_ref()
        if target is None:
            return 0.0
        constraint_cost = 0.0
        if self._sim.get_main_group() is None or self._sim.get_main_group().anchor is None:
            return constraint_cost
        vector_to_pos = position - target.intended_position
        distance_to_sim = vector_to_pos.magnitude()
        if distance_to_sim <= self.minimum_distance:
            return ThrowingGroupCostFunction.INVALID_GOAL_SCORE
        constraint_cost += self._score_location(position)
        vector_to_anchor = position - self._sim.get_main_group().anchor.position
        distance_to_anchor = vector_to_anchor.magnitude_squared()
        constraint_cost = -distance_to_anchor
        distance_to_position = (position - self._sim.intended_position).magnitude()
        if distance_to_position < self.adjustment_distance:
            constraint_cost += ThrowingGroupCostFunction.INVALID_GOAL_SCORE
        return constraint_cost
コード例 #4
0
 def __init__(self, **kwargs):
     super().__init__(verify_tunable_callback=TunableRelationshipTrack2dLink._verify_tunable_callback, y_axis_track=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions='RelationshipTrack', description='The bit track to key the Y axis off of.'), y_axis_content=TunableList(TunableTuple(bit_set=TunableRelationshipBitSet(description='The bit set representing the X axis in the matrix for this Y position.'), remove_value=Tunable(float, -100, description='Track score value for the bit to be removed.'), add_value=Tunable(float, 100, description='Track score value for the bit to be added.'), description='A threshold for this node in the matrix along with a bit set.'), description='A list of bit sets and thresholds.  This represents the Y axis of the matrix.'), **kwargs)
コード例 #5
0
class PaintingStyle(metaclass=TunedInstanceMetaclass,
                    manager=services.get_instance_manager(
                        sims4.resources.Types.RECIPE)):
    __qualname__ = 'PaintingStyle'
    INSTANCE_TUNABLES = {
        '_display_name':
        TunableLocalizedString(
            description=
            '\n                The style name that will be displayed on the hovertip.\n                '
        ),
        '_textures':
        TunableList(
            description=
            '\n                A set of PaintingTextures from which one will be chosen for an\n                artwork created using this PaintingStyle.\n                ',
            tunable=TunableTuple(
                description=
                '\n                    A particular painting texture and a weight indicating how\n                    often it will be picked from among available textures when\n                    this style is used.\n                    ',
                texture=TunableReference(
                    description=
                    '\n                        A particular painting texture to use as part of this\n                        style.\n                        ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.RECIPE),
                    class_restrictions=(PaintingTexture, )),
                weight=TunableRange(
                    float,
                    1.0,
                    minimum=0,
                    description=
                    '\n                        The relative likelihood (among available textures) that\n                        this one will be chosen.\n                        '
                )))
    }

    @classproperty
    def display_name(cls):
        return cls._display_name

    @classmethod
    def pick_texture(cls, crafter, canvas_types) -> PaintingTexture:
        resolver = SingleSimResolver(crafter.sim_info)
        weights = []
        for weighted_texture in cls._textures:
            weight = weighted_texture.weight
            texture = weighted_texture.texture
            while canvas_types & texture.canvas_types:
                if texture.tests.run_tests(resolver):
                    weights.append((weight, texture))
        texture = sims4.random.pop_weighted(weights)
        if texture is None and cls._textures:
            for weighted_texture in cls._textures:
                weight = weighted_texture.weight
                texture = weighted_texture.texture
                while canvas_types & texture.canvas_types:
                    logger.error(
                        'Tuning Error: No texture of {0} passed tests for {1}, defaulting to {2}',
                        cls._textures,
                        crafter.sim_info,
                        texture,
                        owner='nbaker')
                    return texture
            texture = cls._textures[0].texture
            logger.error(
                'Tuning Error: No texture of {0} was correct type for {1}, defaulting to {2}',
                cls._textures,
                crafter.sim_info,
                texture,
                owner='nbaker')
            return texture
        return texture
コード例 #6
0
class TunableSetClockSpeed(XevtTriggeredElement):
    __qualname__ = 'TunableSetClockSpeed'
    SET_DIRECTLY = 1
    REQUEST_SPEED = 2
    UNREQUEST_SS3 = 3
    FACTORY_TUNABLES = {
        'description':
        'Change the game clock speed as part of an interaction.',
        'game_speed_change':
        TunableVariant(
            default='set_speed_directly',
            set_speed_directly=TunableTuple(
                description=
                '\n                When the interaction runs, the clock speed is set directly at\n                the specified time.\n                ',
                locked_args={'set_speed_type': SET_DIRECTLY},
                game_speed=TunableEnumEntry(
                    description=
                    '\n                    The speed to set the game.\n                    ',
                    tunable_type=ClockSpeedMode,
                    default=ClockSpeedMode.NORMAL)),
            try_request_speed=TunableTuple(
                description=
                '\n                Request a change in game speed. When all user sims on the lot\n                have requested the speed, the speed will occur.\n                ',
                locked_args={'set_speed_type': REQUEST_SPEED},
                game_speed=TunableEnumEntry(
                    description=
                    '\n                    The clock speed is requested, and when all user sims on the\n                    lot have requested the speed, the speed will occur.\n                    ',
                    tunable_type=ClockSpeedMode,
                    default=ClockSpeedMode.SPEED3),
                allow_super_speed_three=OptionalTunable(
                    description=
                    '\n                    If enabled, when this interaction is run, the actor sim is\n                    marked as okay to be sped up to super speed 3. When all\n                    instantiated sims are okay to be sped up to super speed 3,\n                    if the game is in speed 3, the game will go into super\n                    speed 3.\n                    ',
                    tunable=TunableTuple(unrequest_speed=OptionalTunable(
                        description=
                        '\n                            If enabled and if the game is still in super speed\n                            3 when the interaction is canceled or ends, this\n                            speed will be requested.\n                            ',
                        tunable=TunableEnumEntry(
                            tunable_type=ClockSpeedMode,
                            default=ClockSpeedMode.NORMAL),
                        enabled_by_default=True)),
                    needs_tuning=True)),
            unrequest_super_speed_three=TunableTuple(
                description=
                '\n                This will directly set the game speed only if super speed three\n                is active. Otherwise, nothing happens.\n                ',
                locked_args={'set_speed_type': UNREQUEST_SS3},
                game_speed=TunableEnumEntry(
                    description=
                    '\n                    This is the speed the game will enter if it was in Super Speed\n                    Three.\n                    ',
                    tunable_type=ClockSpeedMode,
                    default=ClockSpeedMode.NORMAL)))
    }

    def __init__(self,
                 interaction,
                 *args,
                 game_speed_change,
                 sequence=(),
                 **kwargs):
        super().__init__(interaction,
                         sequence=sequence,
                         game_speed_change=game_speed_change,
                         *args,
                         **kwargs)
        self._game_speed_change = game_speed_change
        self._sim_id = None

    def _do_behavior(self):
        if clock.GameClock.ignore_game_speed_requests:
            return
        set_speed_type = self._game_speed_change.set_speed_type
        game_speed = self._game_speed_change.game_speed
        if set_speed_type == self.SET_DIRECTLY:
            services.game_clock_service().set_clock_speed(game_speed)
        elif set_speed_type == self.REQUEST_SPEED:
            allow_ss3 = self._game_speed_change.allow_super_speed_three
            game_speed_params = (game_speed, allow_ss3 is not None)
            if allow_ss3 is not None and allow_ss3.unrequest_speed is not None:
                self.interaction.register_on_cancelled_callback(
                    lambda _: self._unrequest_super_speed_three_mode(
                        allow_ss3.unrequest_speed))
            self._sim_id = self.interaction.sim.id
            services.game_clock_service().register_game_speed_change_request(
                self.interaction.sim, game_speed_params)
        elif set_speed_type == self.UNREQUEST_SS3:
            self._unrequest_super_speed_three_mode(game_speed)
        if clock_handlers.speed_change_archiver.enabled:
            clock_handlers.archive_speed_change(self.interaction,
                                                set_speed_type, game_speed,
                                                True)

    def _unrequest_super_speed_three_mode(self, game_speed):
        if services.get_super_speed_three_service().in_super_speed_three_mode(
        ):
            services.game_clock_service().set_clock_speed(game_speed)

    def _unrequest_speed(self, *_, **__):
        if self._sim_id is not None:
            services.game_clock_service().unregister_game_speed_change_request(
                self._sim_id)
            if clock_handlers.speed_change_archiver.enabled:
                clock_handlers.archive_speed_change(self.interaction, None,
                                                    None, False)

    def _build_outer_elements(self, sequence):
        if self._game_speed_change.set_speed_type == self.REQUEST_SPEED:
            return build_critical_section_with_finally(sequence,
                                                       self._unrequest_speed)
        return sequence
コード例 #7
0
class ServiceNpcRequest(XevtTriggeredElement):
    __qualname__ = 'ServiceNpcRequest'
    MINUTES_ADD_TO_SERVICE_ARRIVAL = 5
    HIRE = 1
    CANCEL = 2
    FACTORY_TUNABLES = {
        'description':
        '\n        Request a service NPC as part of an interaction. Note for timing field:\n        Only beginning and end will work because xevents will trigger\n        immediately on the server for service requests\n        ',
        'request_type':
        TunableVariant(
            description=
            '\n                Specify the type of service NPC Request. You can hire, dismiss,\n                fire, or cancel a service npc.',
            hire=TunableTuple(
                description=
                '\n                A reference to the tuned service npc instance that will be\n                requested at the specified time.',
                locked_args={'request_type': HIRE},
                service=TunableReference(services.service_npc_manager())),
            cancel=TunableTuple(
                locked_args={'request_type': CANCEL},
                service=TunableReference(services.service_npc_manager()),
                description=
                'A reference to the tuned service that will be cancelled. This only really applies to recurring services where a cancelled service will never have any service npcs show up again until re-requested.'
            ),
            default='hire'),
        'notification':
        OptionalTunable(
            description=
            '\n                When enabled, display a notification when the service npc is \n                successfully hired/cancelled.\n                If hired, last token is DateAndTime when service npc will\n                arrive. (usually this is 1)\n                ',
            tunable=NotificationElement.TunableFactory(
                locked_args={
                    'timing': XevtTriggeredElement.LOCKED_AT_BEGINNING
                }))
    }

    def __init__(self,
                 interaction,
                 *args,
                 request_type,
                 notification,
                 sequence=(),
                 **kwargs):
        super().__init__(interaction,
                         request_type=request_type,
                         notification=notification,
                         sequence=sequence,
                         *args,
                         **kwargs)
        self._request_type = request_type
        self.notification = notification
        self._household = interaction.sim.household
        self._service_npc_user_specified_data_id = None
        self._recurring = False
        self._read_interaction_parameters(**interaction.interaction_parameters)

    def _read_interaction_parameters(self,
                                     service_npc_user_specified_data_id=None,
                                     service_npc_recurring_request=False,
                                     **kwargs):
        self._service_npc_user_specified_data_id = service_npc_user_specified_data_id
        self._recurring = service_npc_recurring_request

    def _do_behavior(self):
        request_type = self._request_type.request_type
        service_npc = self._request_type.service
        if service_npc is None:
            return
        service_npc_service = services.current_zone().service_npc_service
        if request_type == self.HIRE:
            finishing_time = service_npc_service.request_service(
                self._household,
                service_npc,
                user_specified_data_id=self.
                _service_npc_user_specified_data_id,
                is_recurring=self._recurring)
            if self.notification is not None and finishing_time is not None:
                finishing_time = finishing_time + create_time_span(
                    minutes=self.MINUTES_ADD_TO_SERVICE_ARRIVAL)
                notification_element = self.notification(self.interaction)
                notification_element.show_notification(
                    additional_tokens=(finishing_time, ))
        elif request_type == self.CANCEL:
            service_npc_service.cancel_service(self._household, service_npc)
            if self.notification is not None:
                notification_element = self.notification(self.interaction)
                notification_element._do_behavior()
コード例 #8
0
class _BasePhotoMode(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'zoom_capability':
        TunableEnumEntry(
            description=
            '\n            The zoom capability of the camera.\n            ',
            tunable_type=ZoomCapability,
            default=ZoomCapability.NO_ZOOM),
        'camera_quality':
        TunableEnumEntry(
            description=
            '\n            The quality of the camera.\n            ',
            tunable_type=CameraQuality,
            default=CameraQuality.CHEAP),
        'hide_photographer':
        Tunable(
            description=
            '\n            Whether or not to hide the photographer during the photo session.\n            ',
            tunable_type=bool,
            default=False),
        'success_chance':
        SuccessChance.TunableFactory(
            description=
            '\n            Percent chance that a photo will be successful.\n            '
        ),
        'camera_position_bone_name':
        Tunable(
            description=
            '\n            Which bone on the photographer to use for the camera position.\n            ',
            tunable_type=str,
            default='',
            allow_empty=True),
        'camera_position_bone_object':
        TunableEnumEntry(
            description=
            '\n            The object that has the bone from which the camera position is\n            obtained. This is usually the photographer sim.\n            ',
            tunable_type=ParticipantTypeSingle,
            default=ParticipantType.Actor),
        'objects_to_hide_tags':
        OptionalTunable(
            description=
            '\n            If enabled, objects that match any of these tags will be hidden in the photo\n            session.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.TAG_SET),
                                     class_restrictions=('TunableTagSet', ))),
        'number_of_photos_override':
        OptionalTunable(
            description=
            '\n            If tuned, the number of photos that the player can take in this photo session.\n            ',
            tunable=Tunable(tunable_type=int, default=5)),
        'filters_disabled':
        Tunable(
            description=
            '\n            Whether or not to disable photo filters.\n            ',
            tunable_type=bool,
            default=False),
        'single_shot_mode':
        Tunable(
            description=
            '\n            Whether or not to only allow the photographer to take one photo\n            per session.\n            ',
            tunable_type=bool,
            default=False),
        'photo_pose':
        ObjectPose.TunableReference(
            description=
            '\n            The pose the sims in the photo will use.\n            '
        ),
        'photographer_sim':
        TunableEnumEntry(
            description=
            '\n            The participant Sim that is the photographer.\n            ',
            tunable_type=ParticipantTypeSingle,
            default=ParticipantType.Actor),
        'order_photo_target_sims':
        Tunable(
            description=
            '\n            If checked, the targets of this TakePhoto will be assigned actors in\n            the asm based on tags on the interactions they are running. If\n            unchecked, they will be assigned in an arbitrary manner (which may\n            not be random).\n            ',
            tunable_type=bool,
            default=True),
        'photo_target_sims_participants':
        TunableList(
            description=
            '\n            The participants whose Sims are the target of the photograph.\n            ',
            tunable=TunableEnumEntry(tunable_type=ParticipantType,
                                     default=ParticipantType.TargetSim)),
        'photo_target_sims_from_situation':
        OptionalTunable(
            description=
            '\n            Tuning to add a group of situation sims as targets of this photo\n            session.\n            ',
            tunable=TunableTuple(
                situation=TunableReference(
                    description=
                    '\n                    The situation in which to look for sims.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        SITUATION)),
                photographer_job=
                OptionalTunable(
                    description=
                    '\n                    If enabled, the job the photographer sim must have in the tuned\n                    situation in order for that situation to be used. Use this\n                    tuning to ensure we are using the correct situation instance.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SITUATION_JOB))),
                target_jobs=TunableSet(
                    description=
                    '\n                    The chosen sims must have one of the following jobs.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SITUATION_JOB)))))
    }

    def create_take_photo_op(self, sims, interaction):
        take_photo_proto = DistributorOps_pb2.TakePhoto()
        self._populate_take_photo_op(sims, interaction, take_photo_proto)
        take_photo_op = GenericProtocolBufferOp(
            DistributorOps_pb2.Operation.TAKE_PHOTO, take_photo_proto)
        return take_photo_op

    def _populate_take_photo_op(self, sims, interaction, take_photo_proto):
        take_photo_proto.camera_mode = self._get_camera_mode()
        take_photo_proto.zoom_capability = self.zoom_capability
        take_photo_proto.camera_quality = self.camera_quality
        take_photo_proto.hide_photographer = self.hide_photographer
        take_photo_proto.success_chance = self.success_chance.get_chance(
            interaction.get_resolver())
        take_photo_proto.camera_position_bone_name = self.camera_position_bone_name
        offset = self._get_offset(interaction)
        take_photo_proto.camera_position_offset.x = offset.x
        take_photo_proto.camera_position_offset.y = offset.y
        take_photo_proto.camera_position_offset.z = offset.z
        take_photo_proto.rotate_target = self.enable_rotate_target(interaction)
        take_photo_proto.filters_disabled = self.filters_disabled
        take_photo_proto.single_shot_mode = self.single_shot_mode
        take_photo_proto.painting_size = self._get_photo_size()
        take_photo_proto.num_photos_per_session = self.number_of_photos_override if self.number_of_photos_override is not None else Photography.NUM_PHOTOS_PER_SESSION
        take_photo_proto.sim_mood_asm_param_name = sims[
            0].get_mood_animation_param_name()
        if self.objects_to_hide_tags is not None:
            objects_to_hide = list(
                obj.id
                for obj in services.object_manager().get_objects_with_tags_gen(
                    *self.objects_to_hide_tags.tags))
            take_photo_proto.objects_to_hide.extend(objects_to_hide)
        bone_object = interaction.get_participant(
            self.camera_position_bone_object)
        if bone_object is not None:
            take_photo_proto.camera_position_bone_object = bone_object.id
        for (index, sim) in enumerate(sims):
            with ProtocolBufferRollback(
                    take_photo_proto.sim_photo_infos) as entry:
                entry.participant_sim_id = sim.sim_id
                entry.participant_sim_position.x = sim.position.x
                entry.participant_sim_position.y = sim.position.y
                entry.participant_sim_position.z = sim.position.z
                if self.photo_pose is not None:
                    if self.photo_pose.asm is not None:
                        entry.animation_pose.asm = get_protobuff_for_key(
                            self.photo_pose.asm)
                        entry.animation_pose.state_name = self.photo_pose.state_name
                        actor_name = self._get_actor_name(index)
                        if actor_name is not None:
                            entry.animation_pose.actor_name = actor_name

    def _get_camera_mode(self):
        raise NotImplementedError(
            'Attempting to call _get_camera_mode() on the base class, use sub-classes instead.'
        )

    def _get_actor_name(self, index):
        return 'x'

    def _get_photo_size(self):
        return PhotoSize.LARGE

    def _get_offset(self, interaction):
        return Vector3.ZERO()

    def enable_rotate_target(self, interaction):
        return True
コード例 #9
0
class AutonomyComponent(Component,
                        HasTunableFactory,
                        AutoFactoryInit,
                        component_name=AUTONOMY_COMPONENT):
    _STORE_AUTONOMY_REQUEST_HISTORY = False

    class TunableSleepSchedule(TunableTuple):
        def __init__(self, *args, **kwargs):
            super().__init__(
                *args,
                schedule=TunableList(tunable=TunableTuple(
                    description=
                    "\n                        Define a Sim's sleep pattern by applying buffs at\n                        certain times before their scheduled work time. If Sim's\n                        don't have a job, define an arbitrary time and define\n                        buffs relative to that.\n                        ",
                    time_from_work_start=Tunable(
                        description=
                        '\n                            The time relative to the start work time that the buff\n                            should be added. For example, if you want the Sim to\n                            gain this static commodity 10 hours before work, set\n                            this value to 10.\n                            ',
                        tunable_type=float,
                        default=0),
                    buff=TunableBuffReference(
                        description=
                        '\n                            Buff that gets added to the Sim.\n                            ',
                        allow_none=True))),
                default_work_time=TunableTimeOfDay(
                    description=
                    "\n                    The default time that the Sim assumes he needs to be at work\n                    if he doesn't have a career. This is only used for sleep.\n                    ",
                    default_hour=9),
                **kwargs)

    FACTORY_TUNABLES = {
        'initial_delay':
        TunableSimMinute(
            description=
            '\n            How long to wait, in Sim minutes, before running autonomy for the\n            first time.\n            ',
            default=5),
        'mixer_interaction_cache_size':
        TunableRange(
            description=
            '\n            The number of mixes to cache during a subaction autonomy request.\n            ',
            tunable_type=int,
            default=3,
            minimum=1),
        'standard_static_commodity_skip_set':
        TunableSet(
            description=
            '\n            A set of static commodities. Any affordances that provide these\n            commodities will be skipped in a standard autonomy run.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                Types.STATIC_COMMODITY))),
        'preroll_affordance_skip_set':
        TunableSet(
            description=
            '\n            A set of affordances to skip when preroll autonomy is run.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                Types.INTERACTION),
                                     pack_safe=True)),
        'sleep_schedule':
        TunableTuple(
            description=
            '\n            Define when Sims are supposed to sleep.\n            ',
            default_schedule=TunableSleepSchedule(
                description=
                "\n                The Sim's default sleep schedule.\n                "
            ),
            trait_overrides=TunableMapping(
                description=
                "\n                If necessary, override sleep patterns based on a Sim's trait. For\n                example, elders might have different patterns than other Sims.\n                \n                Tune these in priority order. The first trait to be encountered\n                determines the pattern.\n                ",
                key_type=TunableReference(
                    manager=services.get_instance_manager(Types.TRAIT),
                    pack_safe=True),
                value_type=TunableSleepSchedule())),
        '_settings_group':
        TunableEnumEntry(
            description=
            '\n            Define which settings apply to this Sim.\n            ',
            tunable_type=AutonomySettingsGroup,
            default=AutonomySettingsGroup.DEFAULT),
        'get_comfortable':
        OptionalTunable(
            description=
            '\n            If enabled, the Sim will attempt to run this interaction whenever\n            their autonomy bucket is empty, provided this interaction is\n            compatible with what they are already running.\n            ',
            tunable=TunableTuple(
                affordance=TunableReference(
                    description=
                    '\n                    The "Get Comfortable" super interaction.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        INTERACTION),
                    class_restrictions=('SuperInteraction', )),
                delay=TunableSimMinute(
                    description=
                    '\n                    How long the Sim waits before executing the "Get\n                    Comfortable" interaction. This allows for other interactions\n                    to execute and interrupt this.\n                    ',
                    default=4)))
    }

    def __init__(self, owner, *args, **kwargs):
        super().__init__(owner, *args, **kwargs)
        self._last_user_directed_action_time = None
        self._last_autonomous_action_time = DateAndTime(0)
        self._last_no_result_time = None
        self._autonomy_skip_sis = set()
        self._autonomy_enabled = False
        self._full_autonomy_alarm_handle = None
        self._get_comfortable_alarm_handle = None
        self._multitasking_roll = UNSET
        self._role_tracker = RoleStateTracker(owner)
        self._full_autonomy_request = None
        self._full_autonomy_element_handle = None
        self._autonomy_anchor = None
        self._sleep_buff_handle = None
        self._sleep_buff_alarms = {}
        self._sleep_buff_reset = None
        self._autonomy_settings = AutonomySettings()
        self._cached_mixer_interactions = []
        self._queued_autonomy_request = None

    def on_add(self):
        self.owner.si_state.on_changed.append(self.reset_multitasking_roll)
        self.owner.si_state.on_changed.append(
            self.invalidate_mixer_interaction_cache)

    def on_remove(self):
        for alarm_handle in self._sleep_buff_alarms.keys():
            alarms.cancel_alarm(alarm_handle)
        if self._full_autonomy_request is not None:
            self._full_autonomy_request.valid = False
            self._full_autonomy_request = None
        if self._sleep_buff_reset is not None:
            alarms.cancel_alarm(self._sleep_buff_reset)
        self.owner.si_state.on_changed.remove(
            self.invalidate_mixer_interaction_cache)
        self.owner.si_state.on_changed.remove(self.reset_multitasking_roll)
        self.on_sim_reset(True)
        self._role_tracker.shutdown()

    @componentmethod
    def get_autonomy_settings_group(self):
        return self._settings_group

    @componentmethod
    def queue_autonomy_request(self, request):
        if request is None:
            logger.error('Attempting to queue up a None autonomy request.')
            return
        self._queued_autonomy_request = request

    def _on_run_full_autonomy_callback(self, handle):
        if self._full_autonomy_element_handle is not None:
            return
        timeline = services.time_service().sim_timeline
        self._full_autonomy_element_handle = timeline.schedule(
            elements.GeneratorElement(self._run_full_autonomy_callback_gen))

    def _run_full_autonomy_callback_gen(self, timeline):
        reping_delay_on_fail = self._queued_autonomy_request.reping_delay_on_fail if self._queued_autonomy_request is not None else None
        try:
            self.set_last_autonomous_action_time(False)
            autonomy_pushed_interaction = yield from self._attempt_full_autonomy_gen(
                timeline)
            self._last_autonomy_result_was_none = not autonomy_pushed_interaction
            if autonomy_pushed_interaction or self._queued_autonomy_request is not None:
                reping_delay_on_fail = None
        except Exception:
            logger.exception(
                'Exception hit while processing FullAutonomy for {}:',
                self.owner,
                owner='rez')
        finally:
            self._full_autonomy_element_handle = None
            self._schedule_next_full_autonomy_update(
                delay_in_sim_minutes=reping_delay_on_fail)

    def _attempt_full_autonomy_gen(self, timeline):
        if self._full_autonomy_request is not None and self._full_autonomy_request.valid:
            logger.debug(
                'Ignoring full autonomy request for {} due to pending request in the queue.',
                self.owner)
            return False
            yield
        if self.to_skip_autonomy():
            if gsi_handlers.autonomy_handlers.archiver.enabled:
                gsi_handlers.autonomy_handlers.archive_autonomy_data(
                    self.owner,
                    'None - Running SIs are preventing autonomy from running: {}'
                    .format(self._autonomy_skip_sis), 'FullAutonomy', None)
            return False
            yield
        if not self._test_full_autonomy():
            return False
            yield
        try:
            selected_interaction = None
            try:
                if self._queued_autonomy_request is not None:
                    self._full_autonomy_request = self._queued_autonomy_request
                    self._queued_autonomy_request = None
                else:
                    self._full_autonomy_request = self._create_autonomy_request(
                    )
                selected_interaction = yield from services.autonomy_service(
                ).find_best_action_gen(timeline,
                                       self._full_autonomy_request,
                                       archive_if_enabled=False)
            finally:
                self._full_autonomy_request.valid = False
            if not self._autonomy_enabled:
                if gsi_handlers.autonomy_handlers.archiver.enabled:
                    gsi_handlers.autonomy_handlers.archive_autonomy_data(
                        self.owner, 'None - Autonomy Disabled', 'FullAutonomy',
                        None)
                return False
                yield
            if not self._test_full_autonomy():
                if selected_interaction:
                    selected_interaction.invalidate()
                return False
                yield
            if selected_interaction is not None:
                if selected_interaction.transition is not None and (
                        self.owner.routing_master is not None
                        and self.owner.routing_master.is_sim
                ) and self.owner.routing_master.transition_controller is not None:
                    selected_interaction.transition.derail(
                        DerailReason.PREEMPTED, self.owner)
                result = self._push_interaction(selected_interaction)
                if not result and gsi_handlers.autonomy_handlers.archiver.enabled:
                    gsi_handlers.autonomy_handlers.archive_autonomy_data(
                        self.owner,
                        'Failed - interaction failed to be pushed {}.'.format(
                            selected_interaction), 'FullAutonomy', None)
                if result:
                    if gsi_handlers.autonomy_handlers.archiver.enabled:
                        gsi_handlers.autonomy_handlers.archive_autonomy_data(
                            self._full_autonomy_request.sim,
                            selected_interaction,
                            self._full_autonomy_request.autonomy_mode_label,
                            self._full_autonomy_request.gsi_data)
                        self._full_autonomy_request.gsi_data = None
                    return True
                    yield
            elif gsi_handlers.autonomy_handlers.archiver.enabled:
                gsi_handlers.autonomy_handlers.archive_autonomy_data(
                    self._full_autonomy_request.sim, 'None',
                    self._full_autonomy_request.autonomy_mode_label,
                    self._full_autonomy_request.gsi_data)
                self._full_autonomy_request.gsi_data = None
            return False
            yield
        finally:
            if selected_interaction is not None:
                selected_interaction.invalidate()
            self._full_autonomy_request = None

    def _test_full_autonomy(self):
        result = FullAutonomy.test(self.owner)
        if not result:
            if gsi_handlers.autonomy_handlers.archiver.enabled:
                gsi_handlers.autonomy_handlers.archive_autonomy_data(
                    self.owner, result.reason, 'FullAutonomy', None)
            return False
        return True

    @componentmethod
    def run_test_autonomy_ping(self,
                               affordance_list=None,
                               commodity_list=None):
        autonomy_request = AutonomyRequest(
            self.owner,
            autonomy_mode=FullAutonomy,
            commodity_list=commodity_list,
            affordance_list=affordance_list,
            skipped_static_commodities=self.standard_static_commodity_skip_set,
            limited_autonomy_allowed=False)
        selected_interaction = services.autonomy_service().find_best_action(
            autonomy_request)
        return selected_interaction

    @componentmethod
    def cancel_actively_running_full_autonomy_request(self):
        if self._full_autonomy_element_handle is not None:
            self._full_autonomy_element_handle.trigger_hard_stop()
            self._full_autonomy_element_handle = None

    @caches.cached
    def is_object_autonomously_available(self, obj, interaction):
        autonomy_rule = self.owner.get_off_lot_autonomy_rule()
        if interaction.context.pick is not None:
            delta = obj.position - interaction.context.pick.location
            if delta.magnitude() <= autonomy_rule.radius:
                return True
        return self.get_autonomous_availability_of_object(obj, autonomy_rule)

    def get_autonomous_availability_of_object(self,
                                              obj,
                                              autonomy_rule,
                                              reference_object=None):
        reference_object = self.owner if reference_object is None else reference_object
        autonomy_type = autonomy_rule.rule
        off_lot_radius = autonomy_rule.radius
        tolerance = autonomy_rule.tolerance
        anchor_tag = autonomy_rule.anchor_tag
        anchor_buff = autonomy_rule.anchor_buff
        if obj is self.owner:
            return True
        if self.owner.locked_from_obj_by_privacy(obj):
            return False
        if autonomy_type == autonomy.autonomy_modifier.OffLotAutonomyRules.UNLIMITED:
            return True
        if obj.is_sim:
            autonomy_service = services.autonomy_service()
            target_delta = obj.intended_position - obj.position
            if target_delta.magnitude(
            ) > autonomy_service.MAX_OPEN_STREET_ROUTE_DISTANCE_FOR_SOCIAL_TARGET:
                return False
            if not obj.is_on_active_lot(tolerance=tolerance):
                distance_from_me = obj.intended_position - self.owner.intended_position
                if distance_from_me.magnitude(
                ) > autonomy_service.MAX_OPEN_STREET_ROUTE_DISTANCE_FOR_INITIATING_SOCIAL:
                    return False
        if self.owner.object_tags_override_off_lot_autonomy_ref_count(
                obj.get_tags()):
            return True
        if autonomy_type == autonomy.autonomy_modifier.OffLotAutonomyRules.RESTRICTED:
            zone = services.current_zone()
            return zone.is_point_in_restricted_autonomy_area(obj.position)
        if autonomy_type == autonomy.autonomy_modifier.OffLotAutonomyRules.ANCHORED:
            obj_intended_postion = obj.intended_position
            obj_level = obj.level

            def is_close_by(position, level):
                if level is UNSET or level == obj_level:
                    delta = obj_intended_postion - position
                    if delta.magnitude() <= off_lot_radius:
                        return True
                return False

            candidates_exist = False
            if self._autonomy_anchor is not None:
                candidates_exist = True
                if is_close_by(*self._autonomy_anchor):
                    return True
            for anchor in services.object_manager().get_objects_with_tag_gen(
                    anchor_tag):
                candidates_exist = True
                if is_close_by(anchor.intended_position, anchor.level):
                    return True
            if anchor_buff is not None:
                for sim in services.sim_info_manager().instanced_sims_gen():
                    if sim.Buffs.has_buff(anchor_buff.buff_type):
                        candidates_exist = True
                        if is_close_by(sim.intended_position, sim.level):
                            return True
            if candidates_exist:
                return False
            logger.warn(
                'Off-lot autonomy rule is ANCHORED, but there was no anchor set for {} or no objects found with anchor tag: {} or anchor buff: {}. Reverting to default behavior.',
                self.owner, anchor_tag, anchor_buff)
            autonomy_type = autonomy.autonomy_modifier.OffLotAutonomyRules.DEFAULT
        if autonomy_type == autonomy.autonomy_modifier.OffLotAutonomyRules.DEFAULT:
            reference_object_on_active_lot = reference_object.is_on_active_lot(
                tolerance=tolerance)
            if reference_object_on_active_lot and obj.is_on_active_lot(
                    tolerance=tolerance):
                return True
            reference_object_on_active_lot = reference_object.is_on_active_lot(
            )
            if reference_object_on_active_lot:
                return False
            delta = obj.position - reference_object.position
            return delta.magnitude() <= off_lot_radius
        if obj.is_on_active_lot(tolerance=tolerance):
            return autonomy_type == autonomy.autonomy_modifier.OffLotAutonomyRules.ON_LOT_ONLY
        else:
            delta = obj.position - reference_object.position
            return delta.magnitude() <= off_lot_radius

    def _create_autonomy_request(self):
        autonomy_request = AutonomyRequest(
            self.owner,
            autonomy_mode=FullAutonomy,
            skipped_static_commodities=self.standard_static_commodity_skip_set,
            limited_autonomy_allowed=False)
        return autonomy_request

    def _push_interaction(self, selected_interaction):
        if AffordanceObjectPair.execute_interaction(selected_interaction):
            if self.get_comfortable is not None:
                get_comfortable_liability = AutonomousGetComfortableLiability(
                    self.owner)
                selected_interaction.add_liability(
                    AutonomousGetComfortableLiability.LIABILITY_TOKEN,
                    get_comfortable_liability)
            return True
        should_log = services.autonomy_service().should_log(self.owner)
        if should_log:
            logger.debug('Autonomy failed to push {}',
                         selected_interaction.affordance)
        if selected_interaction.target:
            self.owner.add_lockout(selected_interaction.target,
                                   AutonomyMode.LOCKOUT_TIME)
        return False

    def _schedule_next_full_autonomy_update(self, delay_in_sim_minutes=None):
        if not self._autonomy_enabled:
            return
        try:
            if delay_in_sim_minutes is None:
                delay_in_sim_minutes = self.get_time_until_next_update()
            logger.assert_log(
                isinstance(delay_in_sim_minutes, TimeSpan),
                'delay_in_sim_minutes is not a TimeSpan object in _schedule_next_full_autonomy_update()',
                owner='rez')
            logger.debug('Scheduling next autonomy update for {} for {}',
                         self.owner, delay_in_sim_minutes)
            self._create_full_autonomy_alarm(delay_in_sim_minutes)
        except Exception:
            logger.exception(
                'Exception hit while attempting to schedule FullAutonomy for {}:',
                self.owner)

    def start_autonomy_alarm(self):
        self._autonomy_enabled = True
        self._schedule_next_full_autonomy_update(
            clock.interval_in_sim_minutes(self.initial_delay))

    def _create_full_autonomy_alarm(self, time_until_trigger):
        if self._full_autonomy_alarm_handle is not None:
            self._destroy_full_autonomy_alarm()
        if time_until_trigger.in_ticks() <= 0:
            time_until_trigger = TimeSpan(1)
        self._full_autonomy_alarm_handle = alarms.add_alarm(
            self,
            time_until_trigger,
            self._on_run_full_autonomy_callback,
            use_sleep_time=False)

    def get_time_until_ping(self):
        if self._full_autonomy_alarm_handle is not None:
            return self._full_autonomy_alarm_handle.get_remaining_time()

    def _destroy_full_autonomy_alarm(self):
        if self._full_autonomy_alarm_handle is not None:
            alarms.cancel_alarm(self._full_autonomy_alarm_handle)
            self._full_autonomy_alarm_handle = None

    @componentmethod
    def get_multitasking_roll(self):
        if self._multitasking_roll is UNSET:
            self._multitasking_roll = random.random()
        return self._multitasking_roll

    @componentmethod
    def reset_multitasking_roll(self, interaction=None):
        if interaction is None or (
                interaction.source is InteractionSource.PIE_MENU
                or interaction.source is InteractionSource.AUTONOMY
        ) or interaction.source is InteractionSource.SCRIPT:
            self._multitasking_roll = UNSET

    @componentmethod
    def set_anchor(self, anchor):
        self._autonomy_anchor = anchor

    @componentmethod
    def clear_anchor(self):
        self._autonomy_anchor = None

    @componentmethod
    def push_get_comfortable_interaction(self):
        if self.get_comfortable is None:
            return False
        if self._get_comfortable_alarm_handle is not None:
            return False

        def _push_get_comfortable_interaction(_):
            self._get_comfortable_alarm_handle = None
            if any(si.is_guaranteed() for si in self.owner.si_state):
                return
            context = InteractionContext(self.owner,
                                         InteractionSource.AUTONOMY,
                                         Priority.Low)
            self.owner.push_super_affordance(self.get_comfortable.affordance,
                                             None, context)

        self._get_comfortable_alarm_handle = alarms.add_alarm(
            self, create_time_span(minutes=self.get_comfortable.delay),
            _push_get_comfortable_interaction)

    def _destroy_get_comfortable_alarm(self):
        if self._get_comfortable_alarm_handle is not None:
            self._get_comfortable_alarm_handle.cancel()
            self._get_comfortable_alarm_handle = None

    @componentmethod
    def on_sim_reset(self, is_kill):
        self.invalidate_mixer_interaction_cache(None)
        if self._full_autonomy_request is not None:
            self._full_autonomy_request.valid = False
        if is_kill:
            self._autonomy_enabled = False
            self._destroy_full_autonomy_alarm()
        self._destroy_get_comfortable_alarm()
        if self._full_autonomy_element_handle is not None:
            self._full_autonomy_element_handle.trigger_hard_stop()
            self._full_autonomy_element_handle = None

    @componentmethod
    def run_full_autonomy_next_ping(self):
        self._last_user_directed_action_time = None
        self._schedule_next_full_autonomy_update(TimeSpan(1))

    @componentmethod
    def set_last_user_directed_action_time(self, to_reschedule_autonomy=True):
        now = services.time_service().sim_now
        logger.debug('Setting user-directed action time for {} to {}',
                     self.owner, now)
        self._last_user_directed_action_time = now
        self._last_autonomy_result_was_none = False
        if to_reschedule_autonomy:
            self._schedule_next_full_autonomy_update()

    @componentmethod
    def set_last_autonomous_action_time(self, to_reschedule_autonomy=True):
        now = services.time_service().sim_now
        logger.debug('Setting last autonomous action time for {} to {}',
                     self.owner, now)
        self._last_autonomous_action_time = now
        self._last_autonomy_result_was_none = False
        if to_reschedule_autonomy:
            self._schedule_next_full_autonomy_update()

    @componentmethod
    def set_last_no_result_time(self, to_reschedule_autonomy=True):
        now = services.time_service().sim_now
        logger.debug('Setting last no-result time for {} to {}', self.owner,
                     now)
        self._last_no_result_time = now
        if to_reschedule_autonomy:
            self._schedule_next_full_autonomy_update()

    @componentmethod
    def skip_autonomy(self, si, to_skip):
        if si.source == InteractionSource.BODY_CANCEL_AOP or (
                si.source == InteractionSource.CARRY_CANCEL_AOP
                or si.source == InteractionSource.SOCIAL_ADJUSTMENT
        ) or si.source == InteractionSource.VEHCILE_CANCEL_AOP:
            return
        if to_skip:
            logger.debug('Skipping autonomy for {} due to {}', self.owner, si)
            self._autonomy_skip_sis.add(si)
        else:
            if si in self._autonomy_skip_sis:
                self._autonomy_skip_sis.remove(si)
            logger.debug('Unskipping autonomy for {} due to {}; {} is left.',
                         self.owner, si, self._autonomy_skip_sis)

    def _get_last_user_directed_action_time(self):
        return self._last_user_directed_action_time

    def _get_last_autonomous_action_time(self):
        return self._last_autonomous_action_time

    def _get_last_no_result_time(self):
        return self._last_no_result_time

    @property
    def _last_autonomy_result_was_none(self):
        return self._last_no_result_time is not None

    @_last_autonomy_result_was_none.setter
    def _last_autonomy_result_was_none(self, value: bool):
        if value == True:
            self.set_last_no_result_time(to_reschedule_autonomy=False)
        else:
            self._last_no_result_time = None

    @componentmethod
    def to_skip_autonomy(self):
        return bool(self._autonomy_skip_sis)

    @componentmethod
    def clear_all_autonomy_skip_sis(self):
        self._autonomy_skip_sis.clear()

    @componentmethod
    def is_player_active(self):
        if self._get_last_user_directed_action_time() is None:
            return False
        else:
            delta = services.time_service(
            ).sim_now - self._get_last_user_directed_action_time()
            if delta >= AutonomyMode.get_autonomy_delay_after_user_interaction(
            ):
                return False
        return True

    @componentmethod
    def get_time_until_next_update(self, mode=FullAutonomy):
        time_to_run_autonomy = None
        if self.is_player_active():
            time_to_run_autonomy = self._get_last_user_directed_action_time(
            ) + mode.get_autonomy_delay_after_user_interaction()
        elif self._last_autonomy_result_was_none:
            time_to_run_autonomy = self._get_last_no_result_time(
            ) + mode.get_no_result_delay_time()
        elif self.owner.has_any_pending_or_running_interactions():
            time_to_run_autonomy = self._get_last_autonomous_action_time(
            ) + mode.get_autonomous_delay_time()
        else:
            time_to_run_autonomy = self._get_last_autonomous_action_time(
            ) + mode.get_autonomous_update_delay_with_no_primary_sis()
        delta_time = time_to_run_autonomy - services.time_service().sim_now
        if delta_time.in_ticks() <= 0:
            delta_time = TimeSpan(1)
        return delta_time

    @componentmethod
    def run_preroll_autonomy(self, ignored_objects):
        sim = self.owner
        sim_info = sim.sim_info
        for (_, modifier) in sim_info.get_statistic_modifiers_gen():
            if modifier.autonomy_modifier.suppress_preroll_autonomy:
                return (None, None)
        if self._queued_autonomy_request is not None:
            autonomy_request = self._queued_autonomy_request
            self._queued_autonomy_request = None
        else:
            current_away_action = sim_info.current_away_action
            if current_away_action is not None:
                commodity_list = current_away_action.get_commodity_preroll_list(
                )
                static_commodity_list = current_away_action.get_static_commodity_preroll_list(
                )
            else:
                commodity_list = None
                static_commodity_list = None
            autonomy_request = PrerollAutonomyRequest(
                self.owner,
                autonomy_mode=PrerollAutonomy,
                commodity_list=commodity_list,
                static_commodity_list=static_commodity_list,
                distance_estimation_behavior=AutonomyDistanceEstimationBehavior
                .IGNORE_DISTANCE,
                ignored_object_list=ignored_objects,
                limited_autonomy_allowed=False,
                autonomy_mode_label_override='PrerollAutonomy')
        selected_interaction = services.autonomy_service().find_best_action(
            autonomy_request)
        if selected_interaction is None:
            return (None, None)
        elif self._push_interaction(selected_interaction):
            return (selected_interaction.affordance,
                    selected_interaction.target)
        return (None, None)

    @componentmethod
    def invalidate_mixer_interaction_cache(self, si):
        if si is not None and not si.visible:
            return
        if autonomy.autonomy_util.info_start_time is not None:
            sub_action_ping_data = autonomy.autonomy_util.sim_id_to_sub_autonomy_ping.get(
                self.owner.id, None)
            if sub_action_ping_data is not None:
                sub_action_ping_data.mixers_cleared += len(
                    self._cached_mixer_interactions)
        for interaction in self._cached_mixer_interactions:
            interaction.invalidate()
        self._cached_mixer_interactions.clear()

    def _should_run_cached_interaction(self, interaction_to_run):
        if interaction_to_run is None:
            return False
        super_interaction = interaction_to_run.super_interaction
        if super_interaction is None or super_interaction.is_finishing:
            return False
        if super_interaction.phase_index is not None and interaction_to_run.affordance not in super_interaction.all_affordances_gen(
                phase_index=super_interaction.phase_index):
            return False
        if interaction_to_run.is_finishing:
            return False
        if self.owner.is_sub_action_locked_out(interaction_to_run.affordance,
                                               interaction_to_run.target):
            return False
        return interaction_to_run.test()

    def _get_number_mixers_cache(self):
        additional_mixers_to_cache = sum(si.additional_mixers_to_cache()
                                         for si in self.owner.si_state
                                         if not si.is_finishing)
        return self.mixer_interaction_cache_size + additional_mixers_to_cache

    @componentmethod
    def run_subaction_autonomy(self):
        if not SubActionAutonomy.test(self.owner):
            if gsi_handlers.autonomy_handlers.archiver.enabled:
                gsi_handlers.autonomy_handlers.archive_autonomy_data(
                    self.owner, 'None - Autonomy Disabled',
                    'SubActionAutonomy',
                    gsi_handlers.autonomy_handlers.EMPTY_ARCHIVE)
            return EnqueueResult.NONE
        attempt_to_use_cache = False
        if gsi_handlers.autonomy_handlers.archiver.enabled:
            caching_info = []
        else:
            caching_info = None
        sub_action_ping_data = None
        if autonomy.autonomy_util.info_start_time is not None:
            sub_action_ping_data = autonomy.autonomy_util.sim_id_to_sub_autonomy_ping.get(
                self.owner.id, None)
        while self._cached_mixer_interactions:
            attempt_to_use_cache = True
            interaction_to_run = self._cached_mixer_interactions.pop(0)
            if self._should_run_cached_interaction(interaction_to_run):
                enqueue_result = AffordanceObjectPair.execute_interaction(
                    interaction_to_run)
                if enqueue_result:
                    if gsi_handlers.autonomy_handlers.archiver.enabled:
                        gsi_handlers.autonomy_handlers.archive_autonomy_data(
                            self.owner,
                            'Using Cache: {}'.format(interaction_to_run),
                            'SubActionAutonomy',
                            gsi_handlers.autonomy_handlers.EMPTY_ARCHIVE)
                    if sub_action_ping_data is not None:
                        sub_action_ping_data.cache_hits += 1
                    return enqueue_result
            if interaction_to_run:
                interaction_to_run.invalidate()
            if caching_info is not None:
                caching_info.append(
                    'Failed to use cache interaction: {}'.format(
                        interaction_to_run))
            if sub_action_ping_data is not None:
                sub_action_ping_data.cache_use_fails += 1
        if caching_info is not None and attempt_to_use_cache:
            caching_info.append('Cache invalid:Regenerating')
        self.invalidate_mixer_interaction_cache(None)
        context = InteractionContext(self.owner, InteractionSource.AUTONOMY,
                                     Priority.Low)
        autonomy_request = AutonomyRequest(self.owner,
                                           context=context,
                                           consider_scores_of_zero=True,
                                           skipped_affordance_list=[],
                                           autonomy_mode=SubActionAutonomy)
        if caching_info is not None:
            caching_info.append('Caching: Mixers - START')
        mixers_to_cache = self._get_number_mixers_cache()
        initial_probability_result = None
        while len(self._cached_mixer_interactions) < mixers_to_cache:
            interaction = services.autonomy_service().find_best_action(
                autonomy_request,
                consider_all_options=True,
                archive_if_enabled=False)
            if interaction is None:
                break
            if caching_info is not None:
                caching_info.append(
                    'caching interaction: {}'.format(interaction))
                if initial_probability_result is None:
                    initial_probability_result = list(
                        autonomy_request.gsi_data[GSIDataKeys.PROBABILITY_KEY])
            self._cached_mixer_interactions.append(interaction)
            autonomy_request.skipped_affordance_list.clear()
            autonomy_request.skip_adding_request_record = True
            if interaction.lock_out_time is not None:
                if not interaction.lock_out_time.target_based_lock_out:
                    autonomy_request.skipped_affordance_list.append(
                        interaction.affordance)
        if caching_info is not None:
            caching_info.append('Caching: Mixers - DONE')
        if autonomy.autonomy_util.info_start_time is not None:
            if sub_action_ping_data is None:
                sub_action_ping_data = autonomy.autonomy_util.SubAutonomyPingData(
                )
            sub_action_ping_data.num_mixers_cached.append(
                (len(self._cached_mixer_interactions), mixers_to_cache))
        if self._cached_mixer_interactions:
            interaction = self._cached_mixer_interactions.pop(0)
            if caching_info is not None:
                caching_info.append('Executing mixer: {}'.format(interaction))
            enqueue_result = AffordanceObjectPair.execute_interaction(
                interaction)
            if caching_info is not None:
                autonomy_request.gsi_data[
                    GSIDataKeys.MIXER_CACHING_INFO_KEY] = caching_info
                autonomy_request.gsi_data[
                    GSIDataKeys.PROBABILITY_KEY] = initial_probability_result
                if enqueue_result:
                    result_info = str(interaction)
                else:
                    result_info = 'None - failed to execute: {}'.format(
                        interaction)
                gsi_handlers.autonomy_handlers.archive_autonomy_data(
                    autonomy_request.sim, result_info,
                    autonomy_request.autonomy_mode_label,
                    autonomy_request.gsi_data)
                autonomy_request.gsi_data = None
            if sub_action_ping_data is not None:
                sub_action_ping_data.cache_hits += 1
                autonomy.autonomy_util.sim_id_to_sub_autonomy_ping[
                    self.owner.id] = sub_action_ping_data
            return enqueue_result
        else:
            return EnqueueResult.NONE

    @componentmethod
    def add_role(self,
                 role_state_type,
                 role_affordance_target=None,
                 situation=None,
                 **kwargs):
        for role_state in self._role_tracker:
            if isinstance(role_state, role_state_type):
                logger.error(
                    'Trying to add duplicate role:{}. Returning current instantiated role.',
                    role_state_type)
                return role_state
        role_state = role_state_type(self.owner)
        self._role_tracker.add_role(
            role_state,
            role_affordance_target=role_affordance_target,
            situation=situation,
            **kwargs)
        return role_state

    @componentmethod
    def remove_role(self, role_state):
        return self._role_tracker.remove_role(role_state)

    @componentmethod
    def remove_role_of_type(self, role_state_type):
        for role_state_priority in self._role_tracker:
            for role_state in role_state_priority:
                if isinstance(role_state, role_state_type):
                    self.remove_role(role_state)
                    return True
        return False

    @componentmethod
    def active_roles(self):
        return self._role_tracker.active_role_states

    @componentmethod
    def reset_role_tracker(self):
        self._role_tracker.reset()

    def _get_sleep_schedule(self):
        for (trait,
             sleep_schedule) in self.sleep_schedule.trait_overrides.items():
            if self.owner.has_trait(trait):
                return sleep_schedule
        return self.sleep_schedule.default_schedule

    @componentmethod
    def update_sleep_schedule(self):
        self._remove_sleep_schedule_buff()
        for alarm_handle in self._sleep_buff_alarms.keys():
            alarms.cancel_alarm(alarm_handle)
        self._sleep_buff_alarms.clear()
        time_span_until_wakeup = self.get_time_until_next_wakeup()
        sleep_schedule = self._get_sleep_schedule()
        most_appropriate_buff = None
        for sleep_schedule_entry in sorted(
                sleep_schedule.schedule,
                key=lambda entry: entry.time_from_work_start,
                reverse=True):
            if time_span_until_wakeup.in_hours(
            ) <= sleep_schedule_entry.time_from_work_start:
                most_appropriate_buff = sleep_schedule_entry.buff
            else:
                time_until_buff_alarm = time_span_until_wakeup - create_time_span(
                    hours=sleep_schedule_entry.time_from_work_start)
                alarm_handle = alarms.add_alarm(
                    self, time_until_buff_alarm, self._add_buff_callback, True,
                    create_time_span(hours=date_and_time.HOURS_PER_DAY))
                self._sleep_buff_alarms[
                    alarm_handle] = sleep_schedule_entry.buff
        if most_appropriate_buff:
            if most_appropriate_buff.buff_type:
                self._sleep_buff_handle = self.owner.add_buff(
                    most_appropriate_buff.buff_type)
        if self._sleep_buff_reset:
            alarms.cancel_alarm(self._sleep_buff_reset)
        self._sleep_buff_reset = alarms.add_alarm(self, time_span_until_wakeup,
                                                  self._reset_alarms_callback)

    @componentmethod
    def get_time_until_next_wakeup(self, offset_time: TimeSpan = None):
        now = services.time_service().sim_now
        time_span_until_wakeup = None
        sim_careers = self.owner.sim_info.careers
        if sim_careers:
            earliest_time = None
            for career in sim_careers.values():
                wakeup_time = career.get_next_wakeup_time()
                if not earliest_time is None:
                    if wakeup_time < earliest_time:
                        earliest_time = wakeup_time
                earliest_time = wakeup_time
            if earliest_time is not None:
                if offset_time is not None:
                    time_to_operate = now + offset_time
                else:
                    time_to_operate = now
                time_span_until_wakeup = time_to_operate.time_till_next_day_time(
                    earliest_time)
        if time_span_until_wakeup is None:
            start_time = self._get_default_sleep_schedule_work_time(
                offset_time)
            time_span_until_wakeup = start_time - now
        if time_span_until_wakeup.in_ticks() <= 0:
            time_span_until_wakeup += TimeSpan(
                date_and_time.sim_ticks_per_day())
            logger.assert_log(time_span_until_wakeup.in_ticks() > 0,
                              'time_span_until_wakeup occurs in the past.')
        return time_span_until_wakeup

    def _add_buff_callback(self, alarm_handle):
        buff = self._sleep_buff_alarms.get(alarm_handle)
        if not buff:
            logger.error(
                "Couldn't find alarm handle in _sleep_buff_alarms dict for sim:{}.",
                self.owner,
                owner='rez')
            return
        self._remove_sleep_schedule_buff()
        if buff:
            if buff.buff_type:
                self._sleep_buff_handle = self.owner.add_buff(buff.buff_type)

    def _reset_alarms_callback(self, _):
        self.update_sleep_schedule()

    def _remove_sleep_schedule_buff(self):
        if self._sleep_buff_handle is not None:
            self.owner.remove_buff(self._sleep_buff_handle)
            self._sleep_buff_handle = None

    def _get_default_sleep_schedule_work_time(self, offset_time):
        now = services.time_service().sim_now
        if offset_time is not None:
            now += offset_time
        sleep_schedule = self._get_sleep_schedule()
        work_time = date_and_time.create_date_and_time(
            days=int(now.absolute_days()),
            hours=sleep_schedule.default_work_time.hour(),
            minutes=sleep_schedule.default_work_time.minute())
        if work_time < now:
            work_time += date_and_time.create_time_span(days=1)
        return work_time

    @componentmethod
    def get_autonomy_state_setting(self) -> autonomy.settings.AutonomyState:
        return self._get_appropriate_autonomy_setting(
            autonomy.settings.AutonomyState)

    @componentmethod
    def get_autonomy_randomization_setting(
            self) -> autonomy.settings.AutonomyRandomization:
        return self._get_appropriate_autonomy_setting(
            autonomy.settings.AutonomyRandomization)

    @componentmethod
    def get_autonomy_settings(self):
        return self._autonomy_settings

    def _get_appropriate_autonomy_setting(self, setting_class):
        autonomy_service = services.autonomy_service()
        setting = autonomy_service.global_autonomy_settings.get_setting(
            setting_class, self.get_autonomy_settings_group())
        if setting != setting_class.UNDEFINED:
            return setting
        if self._role_tracker is not None:
            setting = self._role_tracker.get_autonomy_state()
            if setting != setting_class.UNDEFINED:
                return setting
        if services.current_zone().is_zone_running:
            tutorial_service = services.get_tutorial_service()
            if tutorial_service is not None and tutorial_service.is_tutorial_running(
            ):
                return autonomy.settings.AutonomyState.FULL
        setting = self._autonomy_settings.get_setting(
            setting_class, self.get_autonomy_settings_group())
        if setting != setting_class.UNDEFINED:
            return setting
        household = self.owner.household
        if household:
            setting = household.autonomy_settings.get_setting(
                setting_class, self.get_autonomy_settings_group())
            if setting != setting_class.UNDEFINED:
                return setting
        setting = autonomy_service.default_autonomy_settings.get_setting(
            setting_class, self.get_autonomy_settings_group())
        if setting == setting_class.UNDEFINED:
            logger.error('Sim {} has an UNDEFINED autonomy setting!',
                         self.owner,
                         owner='rez')
        return setting

    @componentmethod
    def debug_reset_autonomy_alarm(self):
        self._schedule_next_full_autonomy_update()

    @componentmethod
    def debug_output_autonomy_timers(self, _connection):
        now = services.time_service().sim_now
        if self._last_user_directed_action_time is not None:
            sims4.commands.output(
                'Last User-Directed Action: {} ({} ago)'.format(
                    self._last_user_directed_action_time,
                    now - self._last_user_directed_action_time), _connection)
        else:
            sims4.commands.output('Last User-Directed Action: None',
                                  _connection)
        if self._last_autonomous_action_time is not None:
            sims4.commands.output(
                'Last Autonomous Action: {} ({} ago)'.format(
                    self._last_autonomous_action_time,
                    now - self._last_autonomous_action_time), _connection)
        else:
            sims4.commands.output('Last Autonomous Action: None', _connection)
        if self._full_autonomy_alarm_handle is not None:
            sims4.commands.output(
                'Full Autonomy: {} from now'.format(
                    self._full_autonomy_alarm_handle.get_remaining_time()),
                _connection)
        else:
            sims4.commands.output('Full Autonomy: None)', _connection)
        if len(self._autonomy_skip_sis) > 0:
            sims4.commands.output("Skipping autonomy due to the follow SI's:",
                                  _connection)
            for si in self._autonomy_skip_sis:
                sims4.commands.output('\t{}'.format(si), _connection)
        else:
            sims4.commands.output('Not skipping autonomy', _connection)

    @componentmethod
    def debug_get_autonomy_timers_gen(self):
        now = services.time_service().sim_now
        if self._full_autonomy_alarm_handle is not None:
            yield ('Full Autonomy', '{}'.format(
                self._full_autonomy_alarm_handle.get_remaining_time()))
        else:
            yield ('Full Autonomy', 'None')
        if self._last_user_directed_action_time is not None:
            yield ('Last User-Directed Action', '{} ({} ago)'.format(
                self._last_user_directed_action_time,
                now - self._last_user_directed_action_time))
        if self._last_autonomous_action_time:
            yield ('Last Autonomous Action', '{} ({} ago)'.format(
                self._last_autonomous_action_time,
                now - self._last_autonomous_action_time))
        if len(self._autonomy_skip_sis) > 0:
            yield ('Skipping Autonomy?', 'True')
        else:
            yield ('Skipping Autonomy?', 'False')

    @componentmethod
    def debug_update_autonomy_timer(self, mode):
        self._schedule_next_full_autonomy_update()
コード例 #10
0
class LightingComponent(Component,
                        HasTunableFactory,
                        AutoFactoryInit,
                        component_name=LIGHTING_COMPONENT,
                        persistence_key=protocols.PersistenceMaster.
                        PersistableData.LightingComponent):
    LIGHT_STATE_STAT = TunableReference(
        description=
        "\n        The stat name used to manipulate the lights' on and off states that\n        control the effects they may or may not play\n        ",
        manager=services.get_instance_manager(sims4.resources.Types.STATISTIC),
        deferred=True)
    MANUAL_LIGHT_TAG = TunableEnumEntry(
        description=
        '\n        The tag that is used to mark lighting objects that do not have any\n        automatic behavior such as following auto-light interactions.\n        ',
        tunable_type=Tag,
        default=Tag.INVALID)
    NON_ELECTRIC_LIGHT_TAG = TunableEnumEntry(
        description=
        '\n        The tag that is used to determine if the lights goes off when the power\n        is shut down.\n        ',
        tunable_type=Tag,
        default=Tag.INVALID)
    LIGHT_AUTOMATION_DIMMER_VALUE = -1
    LIGHT_DIMMER_STAT_MULTIPLIER = 100
    LIGHT_DIMMER_VALUE_OFF = 0.0
    LIGHT_DIMMER_VALUE_MAX_INTENSITY = 1.0
    FACTORY_TUNABLES = {
        'component_interactions':
        TunableList(
            description=
            '\n            Each interaction in this list will be added to the owner of the\n            component.\n            ',
            tunable=TunableReference(manager=services.affordance_manager())),
        'default_dimmer_value':
        TunableRange(
            description=
            '\n            The initial dimmer value for this light when first created.\n            ',
            tunable_type=float,
            default=LIGHT_DIMMER_VALUE_MAX_INTENSITY,
            minimum=LIGHT_DIMMER_VALUE_OFF,
            maximum=LIGHT_DIMMER_VALUE_MAX_INTENSITY),
        'material_state_on':
        OptionalTunable(
            description=
            '\n            If enabled, specify the material state to apply when the light is\n            on.\n            ',
            tunable=Tunable(
                description=
                '\n                The material state to apply when the light is on.\n                ',
                tunable_type=str,
                default='lightson')),
        'material_state_off':
        OptionalTunable(
            description=
            '\n            If enabled, specify the material state to apply when the light is\n            off.\n            ',
            tunable=Tunable(
                description=
                '\n                The material state to apply when the light is off.\n                ',
                tunable_type=str,
                default='lightsoff')),
        'visual_effect':
        OptionalTunable(
            description=
            '\n            If enabled, specify the visual effect to apply when the light is on.\n            ',
            tunable=PlayEffect.TunableFactory()),
        'light_states':
        OptionalTunable(
            description=
            '\n            If enabled then we will set these states when the lighting component is changed due to it being\n            an auto-light.\n            ',
            tunable=TunableTuple(
                description=
                "\n                The states to react to the light's on/off state changing.\n                ",
                on_state_value=TunableReference(
                    description=
                    '\n                    The state value of the state that will be set when this light changes to on.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.OBJECT_STATE),
                    class_restrictions=('ObjectStateValue', )),
                off_state_value=TunableReference(
                    description=
                    '\n                    The state value of the state that will be set when this light changes to off.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.OBJECT_STATE),
                    class_restrictions=('ObjectStateValue', )))),
        'disabling_state_values':
        TunableList(
            description=
            '\n            If tuned, states which will, if active, cause this component to \n            disable. Disabling is the same behavior that is used for electric \n            lights if the power is out.\n            ',
            tunable=TunableReference(
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECT_STATE),
                class_restrictions=('ObjectStateValue', )))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._user_intensity_overrides = None
        self._light_dimmer = self.default_dimmer_value
        self._previous_light_dimmer = None
        self._owner_stat_tracker = self.owner.get_tracker(
            self.LIGHT_STATE_STAT)
        self._material_state_on = self.material_state_on
        self._material_state_off = self.material_state_off
        self._visual_effect = None
        self._disable_because_sold = False
        self._pending_dimmer_value = None
        self._color = None
        self._previous_color = None

    @distributor.fields.ComponentField(op=distributor.ops.SetLightDimmer)
    def light_dimmer(self):
        return self._light_dimmer

    def _resend_dimmer_value(self):
        _distributor = distributor.system.Distributor.instance()
        if _distributor.client is not None and self.owner.valid_for_distribution:
            client_dimmer_value = self._light_dimmer
            if self._light_dimmer == self.LIGHT_AUTOMATION_DIMMER_VALUE:
                if self._user_intensity_overrides:
                    client_dimmer_value = -self._user_intensity_overrides
            op = distributor.ops.SetLightDimmer(client_dimmer_value)
            if op is not None:
                _distributor.add_op(self.owner, op)

    @light_dimmer.setter
    def light_dimmer(self, value):
        if self.is_power_off():
            value = 0
        if value != self._light_dimmer:
            self.set_light_dimmer_value(value)

    @distributor.fields.ComponentField(
        op=distributor.ops.SetLightMaterialStates)
    def light_material_states(self):
        return (self._material_state_on, self._material_state_off)

    @light_material_states.setter
    def light_material_states(self, value):
        material_state_on = getattr(value, 'material_state_on')
        if material_state_on is not None:
            self._material_state_on = self.material_state_on if material_state_on is DEFAULT else material_state_on
        material_state_off = getattr(value, 'material_state_off')
        if material_state_off is not None:
            self._material_state_off = self.material_state_off if material_state_off is DEFAULT else material_state_off

    @distributor.fields.ComponentField(op=distributor.ops.SetLightColor)
    def light_color(self):
        return self._color

    _resend_color = light_color.get_resend()

    @componentmethod
    def is_lighting_enabled(self):
        return self._pending_dimmer_value is None

    @componentmethod
    def get_light_dimmer_value(self):
        return self._light_dimmer

    @componentmethod
    def get_previous_light_dimmer_value(self):
        return self._previous_light_dimmer

    @componentmethod
    def get_overridden_dimmer_value(self, value):
        if value == self.LIGHT_AUTOMATION_DIMMER_VALUE:
            return self.LIGHT_AUTOMATION_DIMMER_VALUE
        if value == self.LIGHT_DIMMER_VALUE_OFF:
            return self.LIGHT_DIMMER_VALUE_OFF
        if self._user_intensity_overrides:
            value = self._user_intensity_overrides
        return sims4.math.clamp(self.LIGHT_DIMMER_VALUE_OFF, float(value),
                                self.LIGHT_DIMMER_VALUE_MAX_INTENSITY)

    @componentmethod
    def set_light_dimmer_value(self, value, store_previous_value=False):
        if store_previous_value:
            self._previous_light_dimmer = self.get_light_dimmer_value()
        value = self.get_overridden_dimmer_value(value)
        self._light_dimmer = value
        self._update_visual_effect()
        if value != LightingComponent.LIGHT_AUTOMATION_DIMMER_VALUE:
            self.on_light_changed(value > 0)
        stat = self._owner_stat_tracker.get_statistic(self.LIGHT_STATE_STAT)
        if stat is not None:
            self._owner_stat_tracker.set_value(
                self.LIGHT_STATE_STAT,
                value * self.LIGHT_DIMMER_STAT_MULTIPLIER)
        self._resend_dimmer_value()

    def _update_visual_effect(self):
        if self.visual_effect is None:
            return
        if self._light_dimmer == 0:
            self._stop_visual_effect()
            return
        if self._light_dimmer == self.LIGHT_AUTOMATION_DIMMER_VALUE:
            auto_on_effect = True
        else:
            auto_on_effect = False
        if self._visual_effect is not None and self._visual_effect.auto_on_effect == auto_on_effect:
            return
        self._stop_visual_effect()
        self._visual_effect = self.visual_effect(self.owner,
                                                 auto_on_effect=auto_on_effect)
        self._visual_effect.start()

    def _stop_visual_effect(self, immediate=False):
        if self._visual_effect is not None:
            self._visual_effect.stop(immediate=immediate)
            self._visual_effect = None

    @componentmethod
    def get_light_color(self):
        return self._color

    @componentmethod
    def get_previous_light_color(self):
        return self._previous_color

    @componentmethod
    def set_light_color(self, color, store_previous_value=False):
        if store_previous_value:
            self._previous_color = self._color
        self._color = color
        self._resend_color()

    @componentmethod
    def set_user_intensity_override(self, value):
        self._user_intensity_overrides = value
        self.set_light_dimmer_value(value)

    @componentmethod
    def on_light_changed(self, on):
        if self.light_states is None:
            return
        if on:
            self.owner.set_state(self.light_states.on_state_value.state,
                                 self.light_states.on_state_value)
        else:
            self.owner.set_state(self.light_states.off_state_value.state,
                                 self.light_states.off_state_value)

    def is_power_off(self):
        if not services.utilities_manager().is_utility_active(
                Utilities.POWER) and not get_object_has_tag(
                    self.owner.definition.id,
                    LightingComponent.NON_ELECTRIC_LIGHT_TAG):
            return True
        return False

    def update_lighting_enabled_state(self):
        if not (not self.is_power_off()
                or get_object_has_tag(self.owner.definition.id,
                                      LightingComponent.NON_ELECTRIC_LIGHT_TAG)
                ) or self.disabling_state_values and any(
                    self.owner.state_value_active(state_value)
                    for state_value in
                    self.disabling_state_values) or self._disable_because_sold:
            old_dimmer_value = self._light_dimmer
            self.set_light_dimmer_value(self.LIGHT_DIMMER_VALUE_OFF)
            if self.is_lighting_enabled():
                self._pending_dimmer_value = old_dimmer_value
        elif not self.is_lighting_enabled():
            new_dimmer_value = self._pending_dimmer_value
            self._pending_dimmer_value = None
            self.set_light_dimmer_value(new_dimmer_value)

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

    def component_interactable_gen(self):
        if self.component_interactions:
            yield self

    @componentmethod
    def get_user_intensity_overrides(self):
        if self._user_intensity_overrides is not None:
            return self._user_intensity_overrides
        return self.LIGHT_DIMMER_VALUE_MAX_INTENSITY

    def on_state_changed(self, state, old_value, new_value, from_init):
        if old_value in self.disabling_state_values or new_value in self.disabling_state_values:
            self.update_lighting_enabled_state()

    def on_add(self):
        if self.owner.is_on_active_lot():
            self.set_light_dimmer_value(self._light_dimmer)
        else:
            self.set_light_dimmer_value(
                LightingComponent.LIGHT_AUTOMATION_DIMMER_VALUE)

    def on_finalize_load(self):
        self.update_lighting_enabled_state()
        self._resend_dimmer_value()

    def on_set_sold(self):
        self._disable_because_sold = True
        self.update_lighting_enabled_state()

    def on_restock(self):
        self._disable_because_sold = False
        self.update_lighting_enabled_state()

    def component_reset(self, reset_reason):
        if reset_reason == ResetReason.BEING_DESTROYED:
            self._stop_visual_effect(immediate=True)

    def save(self, persistence_master_message):
        persistable_data = protocols.PersistenceMaster.PersistableData()
        persistable_data.type = protocols.PersistenceMaster.PersistableData.LightingComponent
        lighting_save = persistable_data.Extensions[
            protocols.PersistableLightingComponent.persistable_data]
        lighting_save.dimmer_setting = self._light_dimmer
        if self._previous_light_dimmer is not None:
            lighting_save.previous_dimmer_setting = self._previous_light_dimmer
        if self._color is not None:
            lighting_save.color = self._color
        if self._previous_color is not None:
            lighting_save.previous_color = self._previous_color
        if self._pending_dimmer_value is not None:
            lighting_save.pending_dimmer_setting = self._pending_dimmer_value
        if self._user_intensity_overrides is not None:
            lighting_save.user_intensity = self._user_intensity_overrides
        persistence_master_message.data.extend([persistable_data])

    def load(self, lighting_component_message):
        lighting_component_data = lighting_component_message.Extensions[
            protocols.PersistableLightingComponent.persistable_data]
        if lighting_component_data.user_intensity:
            self._user_intensity_overrides = lighting_component_data.user_intensity
        self.set_light_dimmer_value(lighting_component_data.dimmer_setting)
        if lighting_component_data.previous_dimmer_setting:
            self._previous_light_dimmer = lighting_component_data.previous_dimmer_setting
        if lighting_component_data.color:
            self.set_light_color(lighting_component_data.color)
        if lighting_component_data.previous_color:
            self._previous_color = lighting_component_data.previous_color
        if lighting_component_data.pending_dimmer_setting:
            self._pending_dimmer_value = lighting_component_data.pending_dimmer_setting
コード例 #11
0
 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)
コード例 #12
0
class ChefsChoice:
    FOOD_COURSES = TunableList(
        description=
        '\n        A List of all the courses to search through in order to find what an \n        NPC will order.\n        ',
        tunable=TunableEnumWithFilter(
            description=
            '\n            A food course that an NPC can order.\n            ',
            tunable_type=Tag,
            filter_prefixes=['recipe_course'],
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True))
    DRINK_COURSE = TunableEnumWithFilter(
        description=
        '\n        The drink course so Sims can order drinks with their meals.\n        ',
        tunable_type=Tag,
        filter_prefixes=['recipe_course'],
        default=Tag.INVALID,
        invalid_enums=(Tag.INVALID, ),
        pack_safe=True)
    DESSERT_COURSE = TunableEnumWithFilter(
        description=
        '\n        The dessert course so Sims can order dessert with their meals.\n        ',
        tunable_type=Tag,
        filter_prefixes=['recipe_course'],
        default=Tag.INVALID,
        invalid_enums=(Tag.INVALID, ),
        pack_safe=True)
    NPC_ORDER_MAP = TunableMapping(
        description=
        '\n        A mapping of tags to weighted tests. If an item on the menu has the\n        designated tag, it will start with the tuned base weight and then each\n        passing test will add the tested-weight to the total weight for that\n        food object. Once all food objects have been weighed for a given\n        category (apps, entrees, etc.), a weighted random determines the\n        winner.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            If the food item has this tag, we will apply the corresponding base\n            weight to it and the sum of the weights of any passing tests run on\n            this object.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            The base weight and weighted tests to run.\n            ',
            base_weight=TunableRange(
                description=
                '\n                The base weight of this food object. Even if no tests pass,\n                this weight will be applied for use with the weighted random\n                selection.\n                ',
                tunable_type=float,
                default=1.0,
                minimum=0),
            weighted_tests=TunableList(
                description=
                '\n                A list of tests and weights. For each passed test, the\n                corresponding weight is added to the base weight of the food\n                object.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Tests and weights. If the test passes, the weight is added\n                    to the base weight of the food object.\n                    ',
                    tests=TunableTestSet(),
                    weight=Tunable(
                        description=
                        '\n                        The weight to add to the base weight of the food object\n                        if the corresponding tests pass. A negative value is\n                        valid.\n                        ',
                        tunable_type=float,
                        default=1.0)))))
    WATER_ORDER_FOR_BACKUP = TunablePackSafeReference(
        description=
        '\n        A reference to the water order that should be available when nothing\n        else is available.\n        ',
        manager=services.recipe_manager(),
        class_restrictions=('Recipe', ))

    @classmethod
    def get_choice_for_npc_sim(cls, sim, course):
        zone_director = get_restaurant_zone_director()
        menu_items = zone_director.get_menu_for_course(course)
        possible_items = cls.get_possible_orders(sim, menu_items)
        if not possible_items:
            return
        choice = random.weighted_random_item(list(possible_items.items()),
                                             flipped=True)
        return choice

    @classmethod
    def get_order_for_npc_sim(cls, sim):
        zone_director = get_restaurant_zone_director()
        if zone_director is None:
            logger.error(
                'Trying to get an order for an NPC sim but there is no restaurant zone director.'
            )
            return
        menu_items = []
        for course in cls.FOOD_COURSES:
            menu_items.extend(zone_director.get_menu_for_course(course))
        possible_orders = list(
            cls.get_possible_orders(sim, menu_items).items())
        food_choice = random.weighted_random_item(possible_orders,
                                                  flipped=True)
        business_manager = services.business_service(
        ).get_business_manager_for_zone()
        if business_manager is not None:
            bucks_tracker = services.active_household().bucks_tracker
            if bucks_tracker.is_perk_unlocked(
                    RestaurantTuning.CUSTOMERS_ORDER_EXPENSIVE_FOOD_PERK_DATA.
                    perk):
                food_choice_2 = random.weighted_random_item(possible_orders,
                                                            flipped=True)
                if food_choice_2 is not food_choice:
                    choice_1_price = business_manager.get_value_with_markup(
                        food_choice.restaurant_base_price)
                    choice_2_price = business_manager.get_value_with_markup(
                        food_choice_2.restaurant_base_price)
                    if choice_2_price > choice_1_price:
                        food_choice = food_choice_2
        drink_choice = cls.get_choice_for_npc_sim(sim, cls.DRINK_COURSE)
        if food_choice is None and drink_choice is None:
            return (None, cls.WATER_ORDER_FOR_BACKUP)
        return (food_choice, drink_choice)

    @classmethod
    def get_order_for_npc_sim_with_menu(cls, sim, menu_preset):
        chef_menu = RestaurantTuning.MENU_PRESETS[menu_preset]
        menu_items = []
        for course in cls.FOOD_COURSES:
            menu_items.extend(chef_menu.recipe_map.get(course, {}))
        possible_orders = cls.get_possible_orders(sim, menu_items)
        food_order = random.weighted_random_item(list(possible_orders.items()),
                                                 flipped=True)
        return food_order

    @classmethod
    def get_possible_orders(cls, sim, menu_items):
        resolver = SingleSimResolver(sim)
        possible_orders = {}
        for (order_data_tag, order_data) in cls.NPC_ORDER_MAP.items():
            for recipe in menu_items:
                if order_data_tag in recipe.recipe_tags:
                    if recipe not in possible_orders:
                        possible_orders[recipe] = 0
                    possible_orders[recipe] += order_data.base_weight
                    for weighted_test in order_data.weighted_tests:
                        if weighted_test.tests.run_tests(resolver):
                            possible_orders[recipe] += weighted_test.weight
        return possible_orders
コード例 #13
0
class RetailZoneDirector(BusinessZoneDirectorMixin, SchedulingZoneDirector):
    INSTANCE_TUNABLES = {'customer_count_curb_appeal_curve': TunableCurve(description='\n            The number of customers we want on the lot based on the curb appeal of\n            the lot. This only determines how many customers we want on the lot.\n            The type of customer is driven by the Customer Data Map and the average\n            value of sellable items on the lot.\n            ', x_axis_name='Curb Appeal', y_axis_name='Customer Count', tuning_group=GroupNames.BUSINESS), 'customer_situations': TunableMapping(description='\n            A mapping that defines which customer situations are spawned based\n            on certain properties of the retail lot.\n            ', key_name='Markup Multiplier', key_type=Tunable(description="\n                The store's price multiplier.\n                ", tunable_type=float, default=1), value_type=TunableList(description='\n                A list of tuple defining the customer data for this multiplier.\n                ', tunable=TunableTuple(required_median_value=TunableInterval(description='\n                        The median value of all items in the store must fall within\n                        this interval, which is inclusive.\n                        ', tunable_type=float, default_lower=0, default_upper=MAX_FLOAT, minimum=0), weighted_situations=TunableList(description='\n                        A list of situations that are available in the specified\n                        markup and price range combination. The situations are\n                        weighted relative to one another within this list.\n                        ', tunable=TunableTuple(situation=RetailCustomerSituation.TunableReference(description="\n                                The situation defining the customer's behavior.\n                                ", pack_safe=True), weight=Tunable(description="\n                                This situation's weight, relative to other\n                                situations in this list.\n                                ", tunable_type=float, default=1))))), tuning_group=GroupNames.BUSINESS), 'employee_situations': TunableList(description='\n            The list of possible employee situations. Right now, one will be\n            assigned at random when the employee comes to work.\n            ', tunable=RetailEmployeeSituation.TunableReference(), tuning_group=GroupNames.BUSINESS), 'npc_employee_situation': RetailEmployeeSituation.TunableReference(description='\n            The situation NPC employees will run.\n            ', tuning_group=GroupNames.BUSINESS)}
    CUSTOMER_SITUATION_LIST_GUID = 258695776
    EMPLOYEE_SITUATION_LIST_GUID = 2967593715

    def _should_create_npc_business_manager(self):
        return True

    def _get_new_npc_business_manager(self):
        npc_business_manager = RetailManager()
        npc_business_manager.set_zone_id(services.current_zone_id())
        npc_business_manager.set_owner_household_id(None)
        return npc_business_manager

    def _load_custom_zone_director(self, zone_director_proto, reader):
        for situation_data_proto in zone_director_proto.situations:
            if situation_data_proto.situation_list_guid == self.CUSTOMER_SITUATION_LIST_GUID:
                self._customer_situation_ids.extend(situation_data_proto.situation_ids)
            elif situation_data_proto.situation_list_guid == self.EMPLOYEE_SITUATION_LIST_GUID:
                self._employee_situation_id_list.extend(situation_data_proto.situation_ids)
        super()._load_custom_zone_director(zone_director_proto, reader)

    def _load_employee_situations(self, zone_director_proto, reader):
        pass

    def _save_custom_zone_director(self, zone_director_proto, writer):
        situation_data_proto = zone_director_proto.situations.add()
        situation_data_proto.situation_list_guid = self.CUSTOMER_SITUATION_LIST_GUID
        situation_data_proto.situation_ids.extend(self._customer_situation_ids)
        if self.business_manager is not None:
            if not self.business_manager.is_owned_by_npc:
                situation_data_proto = zone_director_proto.situations.add()
                situation_data_proto.situation_list_guid = self.EMPLOYEE_SITUATION_LIST_GUID
                for situation_ids in self._employee_situation_ids.values():
                    situation_data_proto.situation_ids.extend(situation_ids)
        super()._save_custom_zone_director(zone_director_proto, writer)

    def _save_employee_situations(self, zone_director_proto, writer):
        pass

    def _get_employee_situation_for_employee_type(self, employee_type):
        return random.choice(self.employee_situations)

    def _get_npc_employee_situation_for_employee_type(self, employee_type):
        return self.npc_employee_situation

    def create_situations_during_zone_spin_up(self):
        is_owned_business = self.business_manager is not None and self.business_manager.owner_household_id is not None
        if is_owned_business and (not self.business_manager.is_owner_household_active and (services.current_zone().time_has_passed_in_world_since_zone_save() or services.current_zone().active_household_changed_between_save_and_load())) and self.business_manager.is_open:
            self._business_manager.start_already_opened_business()
        if is_owned_business:
            return
        super().create_situations_during_zone_spin_up()

    def _get_valid_customer_situations(self, business_manager):
        median_item_value = business_manager.get_median_item_value()
        markup_multiplier = business_manager.markup_multiplier
        for (customer_situation_markup_multiplier, customer_situation_datas) in self.customer_situations.items():
            if almost_equal(markup_multiplier, customer_situation_markup_multiplier):
                break
        else:
            return ()
        valid_situations = []
        resolver = SingleSimResolver(services.active_sim_info())
        for customer_situation_data in customer_situation_datas:
            if median_item_value in customer_situation_data.required_median_value:
                valid_situations.extend((pair.weight, pair.situation) for pair in customer_situation_data.weighted_situations if pair.situation.can_start_situation(resolver))
        return valid_situations

    def _on_customer_situation_request(self):
        self.remove_stale_customer_situations()
        desired_situation_count = self.customer_count_curb_appeal_curve.get(self._business_manager.get_curb_appeal())
        valid_weighted_situations = self._get_valid_customer_situations(self._business_manager)
        if not valid_weighted_situations:
            logger.warn('Tried finding a valid starting situation for customer but no situations matches were found.')
            return
        while desired_situation_count > len(self._customer_situation_ids):
            situation_to_start = weighted_random_item(valid_weighted_situations)
            if situation_to_start is None:
                break
            self.start_customer_situation(situation_to_start)

    @property
    def supported_business_types(self):
        return SUPPORTED_BUSINESS_TYPES
コード例 #14
0
class _PortalTypeDataOceanLadders(RouteEventProviderMixin,
                                  _PortalTypeDataLadders):
    FACTORY_TUNABLES = {
        'climb_up_locations':
        TunableTuple(
            description=
            '\n            Location tunables for climbing up the ladder.\n            ',
            location_start=_PortalLocation.TunableFactory(
                description=
                '\n                The location at the bottom of the ladder where climbing up starts.\n                '
            ),
            location_end=_PortalLocation.TunableFactory(
                description=
                '\n                The location at the top of the ladder where climbing up ends.\n                '
            )),
        'climb_down_locations':
        TunableTuple(
            description=
            '\n            Location tunables for climbing down the ladder.\n            ',
            location_start=_PortalLocation.TunableFactory(
                description=
                '\n                The location at the top of the ladder where climbing down starts.\n                '
            ),
            location_end=_PortalLocation.TunableFactory(
                description=
                '\n                The location at the bottom of the ladder where climbing down ends.\n                '
            )),
        'climb_up_route_event':
        RouteEvent.TunableReference(
            description=
            "\n            The route event to set a posture while climing up the ladder portal.\n            Currently, only Set Posture is supported. If any other route events\n            are tuned here there's a good chance they won't work as expected.\n            "
        ),
        'climb_down_route_event':
        RouteEvent.TunableReference(
            description=
            "\n            The route even tto set a posture while climbing down the ladder portal.\n            Currently, only Set Posture is supported. If any other route events\n            are tuned here there's a good chance they won't work as expected.\n            "
        ),
        'posture_start':
        MobilePosture.TunableReference(
            description=
            '\n            Define the entry posture as you cross through this portal. e.g. For\n            the pool, the start posture is stand.\n            '
        ),
        'posture_end':
        MobilePosture.TunableReference(
            description=
            '\n            Define the exit posture as you cross through this portal.\n            '
        ),
        'route_event_time_offset':
        TunableRange(
            description=
            '\n            The amount of time after the start of the ladder portal to schedule \n            the route event. \n            ',
            tunable_type=float,
            default=0.5,
            minimum=0,
            maximum=1)
    }

    def get_portal_duration(self, portal_instance, is_mirrored, _, age, gender,
                            species):
        return self._calculate_walkstyle_duration(portal_instance, is_mirrored,
                                                  age, gender, species,
                                                  self.WALKSTYLE_SWIM,
                                                  self.WALKSTYLE_WALK)

    def get_portal_locations(self, obj):
        up_start = self.climb_up_locations.location_start(obj)
        up_end = self.climb_up_locations.location_end(obj)
        down_start = self.climb_down_locations.location_start(obj)
        down_end = self.climb_down_locations.location_end(obj)
        locations = [(Location(up_start.position,
                               orientation=up_start.orientation,
                               routing_surface=up_start.routing_surface),
                      Location(up_end.position,
                               orientation=up_end.orientation,
                               routing_surface=up_end.routing_surface),
                      Location(down_start.position,
                               orientation=down_start.orientation,
                               routing_surface=down_start.routing_surface),
                      Location(down_end.position,
                               orientation=down_end.orientation,
                               routing_surface=down_end.routing_surface),
                      PortalFlags.STAIRS_PORTAL_LONG)]
        return locations

    def provide_route_events(self,
                             route_event_context,
                             sim,
                             path,
                             is_mirrored=True,
                             node=None,
                             **kwargs):
        route_event = self.climb_down_route_event if is_mirrored else self.climb_up_route_event
        if not route_event_context.route_event_already_scheduled(
                route_event, provider=self
        ) and not route_event_context.route_event_already_fully_considered(
                route_event, self):
            route_event_context.add_route_event(
                RouteEventType.LOW_SINGLE,
                route_event(provider=self,
                            time=node.time + self.route_event_time_offset))

    def is_route_event_valid(self, route_event, time, sim, path):
        return True

    def get_posture_change(self, portal_instance, is_mirrored,
                           initial_posture):
        if initial_posture is not None and initial_posture.carry_target is not None:
            start_posture = get_origin_spec_carry(self.posture_start)
            end_posture = get_origin_spec_carry(self.posture_end)
        else:
            start_posture = get_origin_spec(self.posture_start)
            end_posture = get_origin_spec(self.posture_end)
        if is_mirrored:
            return (end_posture, start_posture)
        return (start_posture, end_posture)

    def _get_route_ladder_data(self, is_mirrored):
        op = super()._get_route_ladder_data(is_mirrored)
        op.ladder_type = LadderType.LADDER_OCEAN
        return op

    def _get_num_rungs(self, ladder):
        rung_start = self.climb_up_locations.location_start(ladder).position.y
        rung_end = self.climb_up_locations.location_end(
            ladder).position.y - self.ladder_rung_distance
        return (rung_end - rung_start) // self.ladder_rung_distance + 1
コード例 #15
0
class CareerLootOp(BaseLootOperation):
    __qualname__ = 'CareerLootOp'
    REFERENCE = 0
    PARTICIPANT = 1
    OP_PERFORMANCE = 0
    OP_MONEY = 1
    OP_RETIRE = 2
    FACTORY_TUNABLES = {
        'career':
        TunableVariant(
            description=
            '\n            The career to apply loot to.\n            ',
            career_reference=TunableTuple(
                description=
                '\n                Reference to the career.\n                ',
                reference=TunableReference(
                    manager=services.get_instance_manager(
                        sims4.resources.Types.CAREER)),
                locked_args={'id_type': REFERENCE}),
            participant_type=TunableTuple(
                description=
                '\n                The id of the career upon which the op will be applied to. Sim\n                Participant must have the career. Typically should be PickedItemId\n                if this loot is being applied by the continuation of a\n                CareerPickerSuperInteraction.\n                ',
                participant=TunableEnumFlags(
                    enum_type=ParticipantType,
                    default=ParticipantType.PickedItemId),
                locked_args={'id_type': PARTICIPANT}),
            default='career_reference'),
        'operations':
        TunableList(
            description=
            '\n            A list of career loot ops.\n            ',
            tunable=TunableVariant(
                description=
                '\n                What the Sim will get with this op.\n                ',
                performance=TunableTuple(
                    description=
                    "\n                    The tuned amount will be applied to the relevant career's\n                    performance statistic.\n                    ",
                    amount=Tunable(
                        description=
                        "\n                        The amount to apply to the career's performance statistic.\n                        Can be negative.\n                        ",
                        tunable_type=float,
                        default=0),
                    locked_args={'operation_type': OP_PERFORMANCE}),
                money=TunableTuple(
                    description=
                    "\n                    A tuned amount of money, as a multiple of the current\n                    career's simoleons per hour, for the Sim to get.\n                    ",
                    hour_multiplier=Tunable(
                        description=
                        "\n                        The multiplier on the career's simoleons per hour.\n                        ",
                        tunable_type=float,
                        default=0),
                    locked_args={'operation_type': OP_MONEY}),
                retire=TunableTuple(
                    description=
                    '\n                    Retire the Sim from the career. The career will provide a\n                    daily pension until death. All other careers will be quit.\n                    ',
                    locked_args={'operation_type': OP_RETIRE}),
                default='performance'))
    }

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

    def _apply_to_subject_and_target(self, subject, target, resolver):
        if subject is None:
            return
        career = self._get_career(subject, resolver)
        if career is None:
            return
        self._apply_to_career(subject, career, resolver)

    def _apply_to_career(self, sim_info, career, resolver):
        for op in self.operations:
            if op.operation_type == CareerLootOp.OP_PERFORMANCE:
                stat = career.work_performance_stat
                stat.add_value(op.amount, interaction=resolver.interaction)
                career.resend_career_data()
            elif op.operation_type == CareerLootOp.OP_MONEY:
                money = op.hour_multiplier * career.get_hourly_pay()
                sim = sim_info.get_sim_instance(
                    allow_hidden_flags=ALL_HIDDEN_REASONS)
                sim_info.household.funds.add(money,
                                             Consts_pb2.TELEMETRY_MONEY_CAREER,
                                             sim)
            else:
                while op.operation_type == CareerLootOp.OP_RETIRE:
                    sim_info.career_tracker.retire_career(career.guid64)

    def _get_career_id(self, interaction):
        if self.career.id_type == CareerLootOp.REFERENCE:
            return self.career.reference.guid64
        if self.career.id_type == CareerLootOp.PARTICIPANT:
            for career_id in interaction.get_participants(
                    self.career.participant):
                pass
        logger.warn('CareerLootOp: No career found {}',
                    self.career,
                    owner='tingyul')
        return 0

    def _get_career(self, sim_info, interaction):
        career_id = self._get_career_id(interaction)
        if career_id:
            career = sim_info.career_tracker.careers.get(career_id)
            return career
        logger.warn('CareerLootOp: Sim {} does not have career {}'.format(
            sim_info, career_id),
                    owner='tingyul')
コード例 #16
0
class PickServiceNpcSuperInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'service_npcs':
        TunableList(
            description=
            '\n            A list of the service npcs that will show up in the dialog picker\n            ',
            tunable=TunableTuple(
                description=
                '\n                Tuple of service npcs data about those NPCs being pickable.\n                ',
                service_npc=TunableReference(
                    description=
                    '\n                    The service npcs that will show up in the picker.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.SERVICE_NPC),
                    class_restrictions=(ServiceNpcHireable, ),
                    pack_safe=True),
                already_hired_tooltip=TunableLocalizedStringFactory(
                    description=
                    '\n                    Tooltip that displays if the service has already been\n                    hired.\n                    '
                ),
                tests=event_testing.tests.TunableGlobalTestSet(
                    description=
                    '\n                    A set of tests that determine if this service npc will show up\n                    as available or greyed out in the picker.\n                    '
                )),
            tuning_group=GroupNames.PICKERTUNING),
        'non_service_npcs':
        TunableList(
            description=
            "\n            A List of non service NPCs that can be hired using the\n            'Hire A Service' UI.\n            ",
            tunable=TunableTuple(
                description=
                "\n                The Data needed to display the non service NPC in the \n                'Hire A Service' UI.\n                ",
                icon=TunableIconAllPacks(
                    description=
                    "\n                    The icon to be displayed in 'Hire a Service' UI\n                    ",
                    tuning_group=GroupNames.UI),
                name=TunableLocalizedStringFactory(
                    description=
                    "\n                    The name to be displayed for this NPC in the 'Hire a Service'\n                    UI.\n                    "
                ),
                cost_string=TunableVariant(
                    description=
                    '\n                    When enabled, the tuned string will be shown as the cost\n                    of hiring this NPC.\n                    ',
                    cost_amount=Tunable(
                        description='\n                        ',
                        tunable_type=int,
                        default=0),
                    no_cost_string=TunableLocalizedStringFactory(
                        description=
                        "\n                        The description to be used for this NPC in the \n                        if there isn't a cost associated with it\n                        "
                    ),
                    locked_args={'disabled': None},
                    default='disabled'),
                hire_interaction=TunableReference(
                    description=
                    '\n                    The affordance to push the sim making the call when hiring this\n                    service npc from a picker dialog from the phone.\n                    ',
                    manager=services.affordance_manager(),
                    pack_safe=True),
                tests=event_testing.tests.TunableGlobalTestSet(
                    description=
                    '\n                    A set of global tests that are always run before other tests. All\n                    tests must pass in order for the interaction to run.\n                    '
                ),
                free_service_traits=TunableList(
                    description=
                    '\n                    If any Sim in the household has one of these traits, the \n                    non service npc will be free.\n                    ',
                    tunable=TunableReference(manager=services.trait_manager(),
                                             pack_safe=True),
                    unique_entries=True))),
        'display_price_flat_rate':
        TunableLocalizedStringFactory(
            description=
            '\n            Formatting for cost of the service if it has just a one time flat fee.\n            Parameters: 0 is flat rate cost of the service\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_price_hourly_cost':
        TunableLocalizedStringFactory(
            description=
            '\n            Formatting for cost of the service if it is purely hourly\n            Parameters: 0 is hourly cost of the service\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_price_fee_and_hourly_cost':
        TunableLocalizedStringFactory(
            description=
            '\n            Formatting for cost of the service if it has an upfront cost AND an\n            hourly cost\n            Parameters: 0 is upfront cost of service. 1 is hourly cost of service\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_price_free':
        TunableLocalizedString(
            description=
            '\n            Description text if the service has zero upfront cost and zero hourly cost.\n            ',
            tuning_group=GroupNames.PICKERTUNING)
    }

    def _run_interaction_gen(self, timeline):
        self._show_picker_dialog(self.sim, target_sim=self.sim)
        return True
        yield

    class _ServiceNpcRecurringPair:
        def __init__(self, service_npc_type, recurring):
            self.service_npc_type = service_npc_type
            self.recurring = recurring
            self.__name__ = '{} recurring: {}'.format(self.service_npc_type,
                                                      self.recurring)

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = cls if inst is None else inst
        service_npc_data_tuples = [
            service_npc_data for service_npc_data in inst_or_cls.service_npcs
        ]
        for service_npc_data in service_npc_data_tuples:
            service_npc_type = service_npc_data.service_npc
            household = context.sim.household
            service_record = household.get_service_npc_record(
                service_npc_type.guid64, add_if_no_record=False)
            is_enabled = service_record is None or not service_record.hired
            if not is_enabled:
                tooltip = service_npc_data.already_hired_tooltip
            else:
                tooltip = None
            allows_recurring = service_npc_type._recurring is not None
            display_name = service_npc_type.display_name if not allows_recurring else service_npc_type._recurring.one_time_name
            tag = PickServiceNpcSuperInteraction._ServiceNpcRecurringPair(
                service_npc_type, recurring=False)
            if any(
                    sim.trait_tracker.has_any_trait(
                        service_npc_type.free_service_traits)
                    for sim in household.sim_info_gen()):
                display_description = inst_or_cls.display_price_free
            elif not not service_npc_type.cost_up_front > 0 and service_npc_type.cost_hourly > 0:
                display_description = inst_or_cls.display_price_fee_and_hourly_cost(
                    service_npc_type.cost_up_front,
                    service_npc_type.cost_hourly)
            elif service_npc_type.cost_up_front > 0:
                display_description = inst_or_cls.display_price_flat_rate(
                    service_npc_type.cost_up_front)
            elif service_npc_type.cost_hourly > 0:
                display_description = inst_or_cls.display_price_hourly_cost(
                    service_npc_type.cost_hourly)
            else:
                display_description = inst_or_cls.display_price_free
            resolver = inst_or_cls.get_resolver(target,
                                                context,
                                                inst_or_cls,
                                                search_for_tooltip=True)
            result = service_npc_data.tests.run_tests(resolver,
                                                      search_for_tooltip=True)
            is_enabled = result == TestResult.TRUE
            tooltip = result.tooltip
            if not (not is_enabled or not is_enabled) and tooltip is None:
                continue
            row = ui.ui_dialog_picker.ObjectPickerRow(
                is_enable=is_enabled,
                name=display_name,
                icon=service_npc_type.icon,
                row_description=display_description,
                tag=tag,
                row_tooltip=tooltip)
            yield row
            if allows_recurring:
                tag = PickServiceNpcSuperInteraction._ServiceNpcRecurringPair(
                    service_npc_type, recurring=True)
                row = ui.ui_dialog_picker.ObjectPickerRow(
                    is_enable=is_enabled,
                    name=service_npc_type._recurring.recurring_name,
                    icon=service_npc_type.icon,
                    row_description=display_description,
                    tag=tag)
                yield row
        for entry in cls.non_service_npcs:
            if any(
                    sim.trait_tracker.has_any_trait(entry.free_service_traits)
                    for sim in household.sim_info_gen()):
                cost_string = inst_or_cls.display_price_free
            else:
                cost_string = inst_or_cls._get_cost_string(entry)
            resolver = inst_or_cls.get_resolver(target,
                                                context,
                                                inst_or_cls,
                                                search_for_tooltip=True)
            result = entry.tests.run_tests(resolver, search_for_tooltip=True)
            if not result and result.tooltip is None:
                continue
            row = ui.ui_dialog_picker.ObjectPickerRow(
                is_enable=result == TestResult.TRUE,
                name=entry.name(),
                icon=entry.icon,
                row_description=cost_string,
                tag=entry,
                row_tooltip=result.tooltip)
            yield row

    @flexmethod
    def _get_cost_string(cls, inst, entry):
        cost_string = entry.cost_string
        if cost_string is None:
            return
        if isinstance(cost_string, int):
            return LocalizationHelperTuning.get_for_money(cost_string)
        return cost_string()

    def on_choice_selected(self, choice_tag, **kwargs):
        tag = choice_tag
        if tag is not None:
            if isinstance(
                    tag,
                    PickServiceNpcSuperInteraction._ServiceNpcRecurringPair):
                tag.service_npc_type.on_chosen_from_service_picker(
                    self, recurring=tag.recurring)
            elif tag.hire_interaction is not None:
                push_affordance = self.generate_continuation_affordance(
                    tag.hire_interaction)
                for aop in push_affordance.potential_interactions(
                        self.sim, self.context):
                    aop.test_and_execute(self.context)
コード例 #17
0
class _PortalTypeDataDynamicStairs(_PortalTypeDataStairs):
    FACTORY_TUNABLES = {'lanes': TunableList(description='\n            The bones that describe each set of lanes (up and down).\n            ', tunable=TunableTuple(description='\n                Groups of bones that represent the up and down routes for this\n                lane.\n                ', up_begin=portal_location._PortalBoneLocation.TunableFactory(description='\n                    The bone for the beginning of the path up the stairs.\n                    '), up_end=portal_location._PortalBoneLocation.TunableFactory(description='\n                    The bone for the end of the path up the stairs.\n                    '), down_begin=portal_location._PortalBoneLocation.TunableFactory(description='\n                    The bone for the beginning of the path down the stairs.\n                    '), down_end=portal_location._PortalBoneLocation.TunableFactory(description='\n                    The bone for the end of the path down the stairs.\n                    '))), 'stair_count': TunableRange(description='\n            The number of stairs the Sim will traverse for this portal.\n            ', tunable_type=int, default=8, minimum=1)}

    def get_additional_required_portal_flags(self, entry_location, exit_location):
        return PortalFlags.STAIRS_PORTAL_LONG

    def get_stair_count(self, _):
        return self.stair_count

    def get_portal_locations(self, obj):
        locations = []
        for lane in self.lanes:
            up_start = lane.up_begin(obj)
            up_end = lane.up_end(obj)
            down_start = lane.down_begin(obj)
            down_end = lane.down_end(obj)
            locations.append((Location(up_start.position, routing_surface=up_start.routing_surface), Location(up_end.position, routing_surface=up_end.routing_surface), Location(down_start.position, routing_surface=down_start.routing_surface), Location(down_end.position, routing_surface=down_end.routing_surface), PortalFlags.STAIRS_PORTAL_LONG))
        return locations
コード例 #18
0
class SituationSimple(Situation):
    __qualname__ = 'SituationSimple'
    INSTANCE_TUNABLES = {'_phases': TunableList(tunable=TunableSituationPhase(description='\n                    Situation reference.\n                    ')), '_exit_conditions': TunableList(description='\n                A list of condition groups of which if any are satisfied, the group is satisfied.\n                ', tunable=TunableTuple(conditions=TunableList(description='\n                        A list of conditions that all must be satisfied for the\n                        group to be considered satisfied.\n                        ', tunable=TunableSituationCondition(description='\n                            A condition for a situation or single phase.\n                            '))))}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._phase = None
        self._phase_index = -1
        self._exit_condition_manager = interactions.utils.exit_condition_manager.ConditionalActionManager()
        self._phase_exit_condition_manager = interactions.utils.exit_condition_manager.ConditionalActionManager()
        self._phase_duration_alarm_handle = None

    def _destroy(self):
        self._remove_exit_conditions()
        self._remove_phase_exit_conditions()
        super()._destroy()

    def _initialize_situation_jobs(self):
        initial_phase = self.get_initial_phase_type()
        for job_tuning in initial_phase.jobs_gen():
            self._add_job_type(job_tuning[0], job_tuning[1])

    def start_situation(self):
        super().start_situation()
        self._attach_exit_conditions()
        self._transition_to_next_phase()

    def _load_situation_states_and_phases(self):
        super()._load_situation_states_and_phases()
        self._attach_exit_conditions()
        self._load_phase()

    def _save_custom(self, seed):
        super()._save_custom(seed)
        remaining_time = 0 if self._phase_duration_alarm_handle is None else self._phase_duration_alarm_handle.get_remaining_time().in_minutes()
        seed.add_situation_simple_data(self._phase_index, remaining_time)
        return seed

    @classmethod
    def _verify_tuning_callback(cls):
        super()._verify_tuning_callback()
        if len(cls._phases) == 0:
            logger.error('Simple Situation {} has no tuned phases.', cls, owner='sscholl')
        if cls._phases[len(cls._phases) - 1].get_duration() != 0:
            logger.error('Situation {} last phase does not have a duration of 0.', cls, owner='sscholl')

    @classmethod
    def get_tuned_jobs(cls):
        job_list = []
        initial_phase = cls.get_initial_phase_type()
        for job in initial_phase.jobs_gen():
            job_list.append(job[0])
        return job_list

    @classmethod
    def get_initial_phase_type(cls):
        return cls._phases[0]

    @classmethod
    def get_phase(cls, index):
        if cls._phases == None or index >= len(cls._phases):
            return
        return cls._phases[index]

    def _transition_to_next_phase(self, conditional_action=None):
        new_index = self._phase_index + 1
        new_phase = self.get_phase(new_index)
        logger.debug('Transitioning from phase {} to phase {}', self._phase_index, new_index)
        self._remove_phase_exit_conditions()
        self._phase_index = new_index
        self._phase = new_phase
        self._attach_phase_exit_conditions()
        for (job_type, role_state_type) in new_phase.jobs_gen():
            self._set_job_role_state(job_type, role_state_type)
        client = services.client_manager().get_first_client()
        if client:
            output = sims4.commands.AutomationOutput(client.id)
            if output:
                output('SituationPhaseTransition; Phase:{}'.format(new_index))

    def _load_phase(self):
        seedling = self._seed.situation_simple_seedling
        logger.debug('Loading phase {}', seedling.phase_index)
        self._phase_index = seedling.phase_index
        self._phase = self.get_phase(self._phase_index)
        self._attach_phase_exit_conditions(seedling.remaining_phase_time)

    def get_phase_state_name_for_gsi(self):
        return str(self._phase_index)

    def _attach_phase_exit_conditions(self, duration_override=None):
        self._phase_exit_condition_manager.attach_conditions(self, self._phase.exit_conditions_gen(), self._transition_to_next_phase)
        duration = duration_override if duration_override is not None else self._phase.get_duration()
        if duration != 0:
            self._phase_duration_alarm_handle = alarms.add_alarm(self, clock.interval_in_sim_minutes(duration), self._transition_to_next_phase)

    def _remove_phase_exit_conditions(self):
        self._phase_exit_condition_manager.detach_conditions(self)
        if self._phase_duration_alarm_handle is not None:
            alarms.cancel_alarm(self._phase_duration_alarm_handle)
            self._phase_duration_alarm_handle = None

    def _attach_exit_conditions(self):
        self._remove_exit_conditions()
        self._exit_condition_manager.attach_conditions(self, self.exit_conditions_gen(), self._situation_ended_callback)

    def _remove_exit_conditions(self):
        self._exit_condition_manager.detach_conditions(self)

    def exit_conditions_gen(self):
        for ec in self._exit_conditions:
            yield ec

    def _situation_ended_callback(self, conditional_action=None):
        logger.debug('Situation exit condition met: {}', self)
        self._self_destruct()
コード例 #19
0
class ContentSet(HasTunableFactory):
    __qualname__ = 'ContentSet'
    FACTORY_TUNABLES = {
        'description':
        ' \n           This is where you tune any sub actions of this interaction.\n           \n           The interactions here can be tuned as reference to individual\n           affordances, lists of affordances, or phase affordances.\n           \n           Sub actions are affordances that can be run anytime this \n           interaction is active. Autonomy will choose which interaction\n           runs.\n           \n           Using phase affordances you can also tune Quick Time or \n           optional affordances that can appear.\n           ',
        'affordance_links':
        TunableAffordanceLinkList(class_restrictions=('MixerInteraction', )),
        'affordance_lists':
        TunableList(TunableAffordanceListReference()),
        'phase_affordances':
        TunableMapping(
            description=
            '\n            A mapping of phase names to affordance links and affordance lists. \n                      \n            This is also where you can specify an affordance is Quick Time (or\n            an optional affordance) and how many steps are required before an\n            option affordance is made available.\n            ',
            value_type=TunableList(
                TunableTuple(affordance_links=TunableAffordanceLinkList(
                    class_restrictions=('MixerInteraction', )),
                             affordance_lists=TunableList(
                                 TunableAffordanceListReference())))),
        'phase_tuning':
        OptionalTunable(
            TunableTuple(
                description=
                '\n            When enabled, statistic will be added to target and is used to\n            determine the phase index to determine which affordance group to use\n            in the phase affordance.\n            ',
                turn_statistic=Statistic.TunableReference(
                    description=
                    '\n                The statistic used to track turns during interaction.\n                Value will be reset to 0 at the start of each phase.\n                '
                ),
                target=TunableEnumEntry(
                    description=
                    '\n                The participant the affordance will target.\n                ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor)))
    }
    EMPTY_LINKS = None

    def __init__(self, affordance_links, affordance_lists, phase_affordances,
                 phase_tuning):
        self._affordance_links = affordance_links
        self._affordance_lists = affordance_lists
        self.phase_tuning = phase_tuning
        self._phase_affordance_links = []
        for key in sorted(phase_affordances.keys()):
            self._phase_affordance_links.append(phase_affordances[key])

    def _get_all_affordance_for_phase_gen(self, phase_affordances):
        for affordance in phase_affordances.affordance_links:
            yield affordance
        for affordance_list in phase_affordances.affordance_lists:
            for affordance in affordance_list:
                yield affordance

    def all_affordances_gen(self, phase_index=None):
        if phase_index is not None and self._phase_affordance_links:
            phase_index = min(phase_index,
                              len(self._phase_affordance_links) - 1)
            phase = self._phase_affordance_links[phase_index]
            for phase_affordances in phase:
                for affordance in self._get_all_affordance_for_phase_gen(
                        phase_affordances):
                    yield affordance
        else:
            for phase in self._phase_affordance_links:
                for phase_affordances in phase:
                    for affordance in self._get_all_affordance_for_phase_gen(
                            phase_affordances):
                        yield affordance
            for link in self._affordance_links:
                yield link
            for l in self._affordance_lists:
                for link in l:
                    yield link

    @property
    def num_phases(self):
        return len(self._phase_affordance_links)

    def has_affordances(self):
        return bool(self._affordance_links) or (bool(
            self._affordance_lists) or bool(self._phase_affordance_links))
コード例 #20
0
class _PortalTypeDataPosture(_PortalTypeDataBase):
    FACTORY_TUNABLES = {
        'posture_start':
        MobilePosture.TunableReference(
            description=
            '\n            Define the entry posture as you cross through this portal. e.g. For\n            the pool, the start posture is stand.\n            '
        ),
        'routing_surface_start':
        TunableRoutingSurfaceVariant(
            description=
            "\n            The routing surface of the portal's entry position. Sims are on this\n            surface while in the starting posture.\n            "
        ),
        'posture_end':
        MobilePosture.TunableReference(
            description=
            '\n            Define the exit posture as you cross through this portal. e.g. For\n            the pool, the end posture is swim.\n            '
        ),
        'routing_surface_end':
        TunableRoutingSurfaceVariant(
            description=
            "\n            The routing surface of the portal's exit position. Sims are on this\n            surface when in the ending posture.\n            "
        ),
        '_outfit_change':
        OptionalTunable(tunable=TunableOutfitChange(
            description=
            '\n                Define the outfit change that happens when a Sim enters or exits\n                this portal.\n                '
        )),
        'portal_locations':
        TunableVariant(
            description=
            '\n            Define the behavior that determines the entry and exit points.\n            ',
            from_posture=_PortalLocationsFromPosture.TunableFactory(),
            from_tuning=_PortalLocationsFromTuning.TunableFactory(),
            default='from_posture'),
        'species_overrides':
        OptionalTunable(
            description=
            '\n            If enabled, we will override the species we generate these portals\n            for. However, the species are still restricted to those that are\n            supported by the posture. If the species cannot enter the posture,\n            then they will not generate portals.\n            ',
            tunable=TunableEnumSet(
                description=
                '\n                The Species we want this portal to support.\n                ',
                enum_type=SpeciesExtended,
                enum_default=SpeciesExtended.HUMAN,
                invalid_enums=SpeciesExtended.INVALID),
            disabled_value=SpeciesExtended),
        'requires_los_between_entry_and_exit':
        Tunable(
            description=
            '\n            If checked, this portal will only be valid if there is LOS between\n            the entry and exit points. If unchecked, LOS is not required.\n            ',
            tunable_type=bool,
            default=True),
        'buff_asm_parameters':
        TunableMapping(
            description=
            '\n            A mapping of buffs to parameters to set on the actor in the ASM when \n            traversing this portal.\n            ',
            key_type=Buff.TunableReference(
                description=
                '\n                If the Sim has this buff, the corresponding ASM parameters will\n                be set on the ASM for this portal.\n                '
            ),
            value_type=TunableTuple(
                description=
                '\n                The parameter name and value to set on the ASM.\n                ',
                parameter_name=Tunable(
                    description=
                    '\n                    The parameter name.\n                    ',
                    tunable_type=str,
                    default=None),
                parameter_value=Tunable(
                    description=
                    '\n                    The value of the parameter if the Sim has the corresponding\n                    buff.\n                    ',
                    tunable_type=str,
                    default=None)))
    }

    @property
    def portal_type(self):
        return PortalType.PortalType_Animate

    @property
    def outfit_change(self):
        return self._outfit_change

    @property
    def requires_los_between_points(self):
        return self.requires_los_between_entry_and_exit

    def get_portal_duration(self, portal_instance, is_mirrored, walkstyle, age,
                            gender, species):
        stub_actor = StubActor(1, species=species)
        arb = Arb()
        portal_posture = postures.create_posture(self.posture_end, stub_actor,
                                                 GLOBAL_STUB_CONTAINER)
        source_posture = postures.create_posture(self.posture_start,
                                                 stub_actor, None)
        portal_posture.append_transition_to_arb(arb,
                                                source_posture,
                                                locked_params={
                                                    ('age', 'x'): age,
                                                    'is_mirrored': is_mirrored
                                                })
        (_, duration, _) = arb.get_timing()
        return duration

    def get_posture_change(self, portal_instance, is_mirrored,
                           initial_posture):
        if initial_posture is not None and initial_posture.carry_target is not None:
            start_posture = get_origin_spec_carry(self.posture_start)
            end_posture = get_origin_spec_carry(self.posture_end)
        else:
            start_posture = get_origin_spec(self.posture_start)
            end_posture = get_origin_spec(self.posture_end)
        if is_mirrored:
            return (end_posture, start_posture)
        else:
            return (start_posture, end_posture)

    def split_path_on_portal(self):
        return PathSplitType.PathSplitType_Split

    def get_portal_locations(self, obj):
        routing_surface_start = self.routing_surface_start(obj)
        routing_surface_end = self.routing_surface_end(obj)
        portals = self.portal_locations(obj, self.posture_end,
                                        routing_surface_start,
                                        routing_surface_end,
                                        self.species_overrides)
        return portals

    def get_portal_asm_params(self, portal_instance, portal_id, sim):
        if portal_id == portal_instance.back:
            entry_location = portal_instance.back_entry
            exit_location = portal_instance.back_exit
        else:
            entry_location = portal_instance.there_entry
            exit_location = portal_instance.there_exit
        final_entry_height = terrain.get_terrain_height(
            entry_location.position.x, entry_location.position.z,
            entry_location.routing_surface)
        final_entry_position = sims4.math.Vector3(entry_location.position.x,
                                                  final_entry_height,
                                                  entry_location.position.z)
        final_exit_height = terrain.get_terrain_height(
            exit_location.position.x, exit_location.position.z,
            exit_location.routing_surface)
        final_exit_position = sims4.math.Vector3(exit_location.position.x,
                                                 final_exit_height,
                                                 exit_location.position.z)
        params = {
            ('InitialTranslation', 'x'): final_entry_position,
            ('InitialOrientation', 'x'): entry_location.orientation,
            ('TargetTranslation', 'x'): final_exit_position,
            ('TargetOrientation', 'x'): exit_location.orientation
        }
        if sim is not None:
            if self.buff_asm_parameters:
                for (buff, param) in self.buff_asm_parameters.items():
                    if sim.has_buff(buff.buff_type):
                        params.update({
                            (param.parameter_name, 'x'):
                            param.parameter_value
                        })
        return params
コード例 #21
0
 def __init__(self, **kwargs):
     super().__init__(TunableTuple(verify_tunable_callback=TunableRelationshipBitSet._verify_tunable_callback, bit=TunableReference(services.get_instance_manager(sims4.resources.Types.RELATIONSHIP_BIT), description='Reference to bit in set'), remove_value=Tunable(description='\n                Track score value for the bit to be removed.\n                Since by default all relationships will converge at 0 we \n                must tune depending on the side of the zero we are in.\n                For values greater than 0, this must be less than add value.\n                For values less than 0, this must be greater than add value. \n                \n                For example, on the friendship track:\n                GOOD_FRIENDS (value>0) has a remove value of 55.\n                As soon as the track value goes below 55 the bit good friends\n                will be removed, and the next lowest bit will be added.\n                \n                DISLIKED (value<0) has a remove_value of -15.\n                As soon as the track value goes over -15 the bit disliked will \n                be removed and the next highest bit will be added.\n                \n                TUNING MIDDLE VALUES (Ranges approach 0)\n                When tuning a value that goes past 0 (a bit from 10 to -10) it\n                is recommended we tune a positive Bit (10 to 0) and a negative \n                bit (-10 to 0).  This way, we can guarantee the rules will \n                consider correct positive and negative directions.\n                ', tunable_type=float, default=-100), add_value=Tunable(description='\n                Track score value for the bit to be added.\n                Since by default all relationships will converge at 0 we \n                must tune depending on the side of the zero we are in.\n                For values greater than 0, this must be greater than remove \n                value.\n                For values less than 0 this must be less than remove value. \n                \n                Example: For the friendship track:\n                GOOD_FRIENDS (value>0) has an add value of 60\n                As soon as the track value goes >= 60 the bit good friends\n                will be added and the previous active track bit will be removed.\n                \n                DISLIKED (value<0) has an add_value of -20\n                As soon as the track value goes <= -20 the bit disliked will \n                be added and the previous active track bid will be removed.\n                \n                TUNING MIDDLE VALUES (Ranges approach 0)\n                When tuning a value that goes past 0 (a bit from 10 to -10) it\n                is recommended we tune a positive Bit (10 to 0) and a negative \n                bit (-10 to 0).  This way, we can guarantee the rules will \n                consider correct positive and negative directions.\n                ', tunable_type=float, default=100), description='Data for this bit in the track'), **kwargs)
コード例 #22
0
class ServiceNpcButlerSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        '_butler_job':
        TunableSituationJobAndRoleState(
            description=
            '\n                The job for Butler in this situation and the \n                corresponding starting role state for service Sim.\n                ',
            display_name='Butler Npc Job'),
        'butler_states':
        TunableTuple(
            cleaning_state=_ButlerCleaningState.TunableFactory(
                description=
                '\n                Situation State for the butler to run all the clean \n                interactions.\n                '
            ),
            gardening_state=_ButlerGardeningState.TunableFactory(
                description=
                '\n                Situation State for the butler to run all the gardening\n                interactions.\n                '
            ),
            childcare_state=_ButlerChildcareState.TunableFactory(
                description=
                '\n                Situation State for the butler to run all the childcare\n                interactions.\n                '
            ),
            repair_state=_ButlerRepairState.TunableFactory(
                description=
                '\n                Situation State for the butler to run all the repair\n                interactions.\n                '
            ),
            default_state=_ButlerDefaultState.TunableFactory(
                description=
                '\n                Situation State for the butler to run all its default\n                interaction when no other service state is selected.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'finish_job_states':
        TunableMapping(
            description=
            '\n            Tune pairs of job finish role states with job finish tests. When\n            those tests pass, the sim will transition to the paired role state.\n            The situation will also be transitioned to the Leaving situation\n            state.\n            ',
            key_type=ServiceNpcEndWorkReason,
            value_type=TunableFinishJobStateAndTest())
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        return (SituationStateData(1,
                                   _ButlerDefaultState,
                                   factory=cls.butler_states.default_state),
                SituationStateData(2,
                                   _ButlerCleaningState,
                                   factory=cls.butler_states.default_state),
                SituationStateData(3,
                                   _ButlerGardeningState,
                                   factory=cls.butler_states.default_state),
                SituationStateData(4,
                                   _ButlerChildcareState,
                                   factory=cls.butler_states.default_state),
                SituationStateData(5,
                                   _ButlerRepairState,
                                   factory=cls.butler_states.default_state),
                SituationStateData(6,
                                   LeaveSituationState,
                                   factory=cls.butler_states.default_state))

    @classmethod
    def default_job(cls):
        return cls._butler_job.job

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return list(cls.butler_states.default_state._tuned_values.
                    job_and_role_changes.items())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._locked_states = set()
        reader = self._seed.custom_init_params_reader
        self._service_npc_type = services.service_npc_manager().get(
            reader.read_uint64('service_npc_type_id', 0))
        if self._service_npc_type is None:
            raise ValueError(
                'Invalid service npc type for situation: {}'.format(self))
        self._hiring_household = services.household_manager().get(
            reader.read_uint64('household_id', 0))
        if self._hiring_household is None:
            raise ValueError(
                'Invalid household for situation: {}'.format(self))
        self._service_start_time = services.time_service().sim_now

    def start_situation(self):
        super().start_situation()
        self._change_state(self.butler_states.default_state())

    def try_set_next_state(self, new_situation_state):
        if new_situation_state.situation_state in self._locked_states:
            new_situation_state.owner = self
            self.try_set_next_state(new_situation_state.next_state())
            return
        self._change_state(new_situation_state)

    def service_sim(self):
        sim = next(self.all_sims_in_situation_gen(), None)
        return sim

    def enable_situation_state(self, new_situation_state):
        if new_situation_state in self._locked_states:
            self._locked_states.remove(new_situation_state)
        services.get_event_manager().process_event(
            TestEvent.AvailableDaycareSimsChanged,
            sim_info=self.service_sim().sim_info)

    def disable_situation_state(self, new_situation_state):
        self._locked_states.add(new_situation_state)
        if self._cur_state.situation_state == new_situation_state:
            self.try_set_next_state(self._cur_state)
        services.get_event_manager().process_event(
            TestEvent.AvailableDaycareSimsChanged,
            sim_info=self.service_sim().sim_info)

    @property
    def is_in_childcare_state(self):
        return ButlerSituationStates.CHILDCARE not in self._locked_states

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        writer.write_uint64('household_id', self._hiring_household.id)
        writer.write_uint64('service_npc_type_id',
                            self._service_npc_type.guid64)

    def _on_set_sim_job(self, sim, job_type):
        service_record = self._hiring_household.get_service_npc_record(
            self._service_npc_type.guid64)
        service_record.add_preferred_sim(sim.sim_info.id)
        self._hiring_household.object_preference_tracker.update_preference_if_possible(
            sim.sim_info)
        self._service_npc_type.on_service_sim_entered_situation(sim, self)
        services.get_event_manager().process_event(
            TestEvent.AvailableDaycareSimsChanged,
            sim_info=self.service_sim().sim_info)
        services.current_zone().service_npc_service.register_service_npc(
            sim.id, self._service_npc_type)

    def _on_leaving_situation(self, end_work_reason):
        service_npc_type = self._service_npc_type
        household = self._hiring_household
        try:
            now = services.time_service().sim_now
            time_worked = now - self._service_start_time
            time_worked_in_hours = time_worked.in_hours()
            cost = service_npc_type.get_cost(time_worked_in_hours)
            if cost > 0:
                (paid_amount,
                 billed_amount) = service_npc_type.try_charge_for_service(
                     household, cost)
                if billed_amount:
                    end_work_reason = ServiceNpcEndWorkReason.NOT_PAID
            else:
                billed_amount = 0
            service_record = household.get_service_npc_record(
                service_npc_type.guid64)
            service_record.time_last_finished_service = now
            self._send_leave_notification(end_work_reason, paid_amount,
                                          billed_amount)
            service_sim = self.service_sim()
            if (end_work_reason == ServiceNpcEndWorkReason.FIRED
                    or end_work_reason == ServiceNpcEndWorkReason.NOT_PAID
                ) and service_record is not None:
                service_record.add_fired_sim(service_sim.id)
                service_record.remove_preferred_sim(service_sim.id)
                services.current_zone(
                ).service_npc_service.on_service_sim_fired(
                    service_sim.id, service_npc_type)
            self._hiring_household.object_preference_tracker.clear_sim_restriction(
                service_sim.id)
            services.current_zone().service_npc_service.cancel_service(
                household, service_npc_type)
        except Exception as e:
            logger.exception(
                'Exception while executing _on_leaving_situation for situation {}',
                self,
                exc=e)
        finally:
            services.current_zone().service_npc_service.cancel_service(
                household, service_npc_type)
        return end_work_reason

    def _send_leave_notification(self, end_work_reason, *localization_args):
        end_work_tuning = self.finish_job_states[end_work_reason]
        notification = end_work_tuning.notification
        if notification is None:
            return
        for client in services.client_manager().values():
            recipient = client.active_sim
            if recipient is not None:
                dialog = notification(recipient)
                dialog.show_dialog(additional_tokens=localization_args,
                                   icon_override=IconInfoData(
                                       obj_instance=self.service_sim()))
                break
コード例 #23
0
ファイル: loot.py プロジェクト: johndpope/sims4-ai-engine
class WeightedSingleSimLootActions(HasTunableReference, HasTunableSingletonFactory, AutoFactoryInit, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.ACTION)):
    __qualname__ = 'WeightedSingleSimLootActions'
    INSTANCE_TUNABLES = {'loot_actions': TunableList(description='\n            A list of weighted Loot Actions that operate only on one Sim.\n            ', tunable=TunableTuple(buff_loot=DynamicBuffLootOp.TunableFactory(), weight=Tunable(description='\n                    Accompanying weight of the loot.\n                    ', tunable_type=int, default=1)))}

    def __iter__(self):
        return iter(self.loot_actions)

    @classmethod
    def pick_loot_op(cls):
        weighted_loots = [(loot.weight, loot.buff_loot) for loot in cls.loot_actions]
        loot_op = sims4.random.weighted_random_item(weighted_loots)
        return loot_op
コード例 #24
0
class RoyaltyTracker(SimInfoTracker):
    PAYMENT_SCHEDULE = WeeklySchedule.TunableFactory(
        description=
        '\n        The schedule for when payments should be made. This is global to all\n        sims that are receiving royalties..\n        '
    )
    ROYALTY_TYPE_DATA = TunableMapping(
        key_type=TunableEnumEntry(
            description='\n            The type of royalty.\n            ',
            tunable_type=RoyaltyType,
            default=RoyaltyType.INVALID,
            invalid_enums=(RoyaltyType.INVALID, )),
        value_type=TunableTuple(
            description=
            '\n            The data associated with the mapped royatly type.\n            ',
            royalty_name=TunableLocalizedString(
                description=
                '\n                The localized name of the RoyaltyType. This is how it will show up in the\n                Royalty notification to the player.\n                '
            ),
            one_time_bonus=OptionalTunable(
                description=
                '\n                If enabled, allows tuning a bonus to the next royalty payment if\n                the Sim has the corresponding buff.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Tuning that provides a bonus to a single royalty payment.\n                    ',
                    bonus_buff=TunablePackSafeReference(
                        description=
                        '\n                        If the sim has this buff when a royalty payment happens,\n                        that payment will be multiplied by the tuned Bonus\n                        Multiplier. This buff will then be removed from the Sim.\n                        ',
                        manager=services.buff_manager()),
                    bonus_multiplier=TunableRange(
                        description=
                        '\n                        The amount to multiply the next royalty payment by if the\n                        Sim has the tuned Bonus Buff.\n                        ',
                        tunable_type=float,
                        default=2,
                        minimum=0)))),
        verify_tunable_callback=_verify_tunable_callback)
    ROYALTY_ENTRY_ITEM = TunableLocalizedStringFactory(
        description=
        '\n        The localized string for a royalty entry.\n        {0.String}: {1.Money}\n        '
    )
    ROYALTY_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        The notification displayed when royalties are viewed.\n        ',
        locked_args={'text': None})

    def __init__(self, sim_info):
        self._sim_ref = weakref.ref(sim_info)
        self._royalties = {}

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

    @property
    def has_royalties(self):
        for (_, royalty_list) in self._royalties.items():
            if royalty_list:
                return True
        return False

    def clear_royalty_tracker(self):
        self._royalties.clear()

    def start_royalty(self,
                      royalty_type,
                      royalty_guid64,
                      entry_name,
                      multiplier,
                      starting_payment=0):
        if royalty_type not in self._royalties.keys():
            self._royalties[royalty_type] = []
        self._royalties[royalty_type].append(
            Royalty(royalty_guid64, entry_name, multiplier, starting_payment))

    def update_royalties_and_get_paid(self):
        if not self.has_royalties:
            return
        sim_info = self.sim_info
        if sim_info is None:
            logger.error(
                'Trying to pay out a Sim but the Sim is None. Perhaps they died? Clearing out royalties for this Sim. Sim: {}',
                sim_info)
            self._royalties.clear()
            return
        tag_payment_map = {}
        royalty_payment_dict = collections.defaultdict(list)
        for (royalty_type, royalty_list) in self._royalties.items():
            bonus_buff = None
            bonus_multiplier = None
            bonus_royalty = RoyaltyTracker.ROYALTY_TYPE_DATA.get(
                royalty_type).one_time_bonus
            if bonus_royalty is not None:
                bonus_buff = bonus_royalty.bonus_buff.buff_type
                if sim_info.Buffs.has_buff(bonus_buff):
                    bonus_multiplier = bonus_royalty.bonus_multiplier
            for royalty in reversed(royalty_list):
                royalty_tuning = RoyaltyPayment.get_royalty_payment_tuning(
                    royalty.royalty_guid64)
                if royalty_tuning is None:
                    logger.error(
                        'royalty_tuning is none for sim {}. royalty: {}.',
                        sim_info, royalty)
                elif royalty.update(royalty_tuning):
                    payment_tag = royalty_tuning.payment_tag
                    payment_amount = RoyaltyTracker.get_payment_amount(
                        royalty, royalty_tuning)
                    if bonus_multiplier is not None:
                        payment_amount *= bonus_multiplier
                    royalty_payment_dict[royalty] = payment_amount
                    if payment_tag not in tag_payment_map:
                        tag_payment_map[payment_tag] = 0
                    tag_payment_map[payment_tag] += payment_amount
                else:
                    royalty_list.remove(royalty)
            if bonus_multiplier is not None:
                sim_info.Buffs.remove_buff_by_type(bonus_buff)
        for (payment_tag, payment_amount) in tag_payment_map.items():
            tags = None
            if payment_tag != tag.Tag.INVALID:
                tags = frozenset((payment_tag, ))
            sim_info.household.funds.add(
                payment_amount,
                Consts_pb2.TELEMETRY_MONEY_ROYALTY,
                sim_info.get_sim_instance(
                    allow_hidden_flags=ALL_HIDDEN_REASONS),
                tags=tags)
        if royalty_payment_dict:
            self.show_royalty_notification(royalty_payment_dict)

    def show_royalty_notification(self, royalty_payment_dict):
        notification_text = LocalizationHelperTuning.get_new_line_separated_strings(
            *(LocalizationHelperTuning.get_bulleted_list(
                RoyaltyTracker.get_name_for_type(royalty_type),
                *(RoyaltyTracker.get_line_item_string(r.entry_name,
                                                      royalty_payment_dict[r])
                  for r in royalty_list))
              for (royalty_type, royalty_list) in self._royalties.items()
              if royalty_list))
        sim_info = self.sim_info
        resolver = SingleSimResolver(sim_info)
        dialog = self.ROYALTY_NOTIFICATION(sim_info,
                                           resolver,
                                           text=lambda *_: notification_text)
        dialog.show_dialog()

    @staticmethod
    def get_name_for_type(royalty_type):
        return RoyaltyTracker.ROYALTY_TYPE_DATA.get(royalty_type).royalty_name

    @staticmethod
    def get_payment_amount(royalty, royalty_tuning):
        deviation_percent = royalty_tuning.payment_deviation_percent
        payment_amount = royalty_tuning.pay_curve.get(
            royalty.current_payment) * royalty.multiplier
        if deviation_percent == 0:
            return int(payment_amount)
        deviation = payment_amount * deviation_percent
        min_payment = payment_amount - deviation
        max_payment = payment_amount + deviation
        return int(sims4.random.uniform(min_payment, max_payment))

    @staticmethod
    def get_line_item_string(name, amount):
        return RoyaltyTracker.ROYALTY_ENTRY_ITEM(name, amount)

    def save(self):
        data = protocols.PersistableRoyaltyTracker()
        for (royalty_type, royalty_list) in self._royalties.items():
            for royalty in royalty_list:
                with ProtocolBufferRollback(data.royalties) as royalty_data:
                    royalty_data.royalty_type = int(royalty_type)
                    royalty_data.royalty_guid64 = royalty.royalty_guid64
                    royalty_data.entry_name = royalty.entry_name
                    royalty_data.multiplier = royalty.multiplier
                    royalty_data.current_payment = royalty.current_payment
        return data

    def load(self, data):
        for royalty_data in data.royalties:
            entry_name = Localization_pb2.LocalizedString()
            entry_name.MergeFrom(royalty_data.entry_name)
            self.start_royalty(royalty_type=RoyaltyType(
                royalty_data.royalty_type),
                               royalty_guid64=royalty_data.royalty_guid64,
                               entry_name=entry_name,
                               multiplier=royalty_data.multiplier,
                               starting_payment=royalty_data.current_payment)

    @classproperty
    def _tracker_lod_threshold(cls):
        return SimInfoLODLevel.FULL

    def on_lod_update(self, old_lod, new_lod):
        if new_lod < self._tracker_lod_threshold:
            self.clear_royalty_tracker()
        elif old_lod < self._tracker_lod_threshold:
            sim_msg = services.get_persistence_service().get_sim_proto_buff(
                self._sim_ref().id)
            if sim_msg is not None:
                self.load(sim_msg.attributes.royalty_tracker)
コード例 #25
0
class EnterCarryWhileHolding(elements.ParentElement, HasTunableFactory,
                             AutoFactoryInit):
    class TrackOverrideExplicit(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'carry_track':
            TunableEnumEntry(
                description=
                '\n                Which hand to carry the object in.\n                ',
                tunable_type=PostureTrack,
                default=PostureTrack.RIGHT,
                invalid_enums=(PostureTrack.BODY, ))
        }

        def get_override(self, *args, **kwargs):
            return self.carry_track

    class TrackOverrideHandedness(HasTunableSingletonFactory, AutoFactoryInit):
        def get_override(self, interaction, sim_participant, *args, **kwargs):
            carry_participant = interaction.get_participant(sim_participant)
            if carry_participant is None:
                return
            hand = carry_participant.get_preferred_hand()
            return hand_to_track(hand)

    NONE = 1
    OBJECT_TO_BE_CARRIED = 2
    PARTICIPANT_TYPE = 3
    FACTORY_TUNABLES = {
        'carry_obj_participant_type':
        TunableEnumEntry(
            description=
            '\n            The object that will be carried.\n            ',
            tunable_type=ParticipantType,
            default=ParticipantType.CarriedObject),
        'sim_participant_type':
        TunableEnumEntry(
            description=
            '\n            The Sim that will get a new carry.\n            ',
            tunable_type=ParticipantTypeSingleSim,
            default=ParticipantTypeSingleSim.Actor),
        'target':
        TunableVariant(
            description=
            '\n            Specify what to use as the target of\n            the owning affordance.\n            ',
            object_to_be_carried=TunableTuple(
                description=
                '\n                Target is the object that WILL be carried.\n                ',
                locked_args={'target_type': OBJECT_TO_BE_CARRIED}),
            none=TunableTuple(
                description=
                '\n                Target is None\n                ',
                locked_args={'target_type': NONE}),
            participant_type=TunableTuple(
                description=
                '\n                Target is the specified participant of THIS interaction.\n                \n                This is necessary if we need to target another participant\n                when we push the owning affordance\n                ',
                participant=TunableEnumEntry(
                    tunable_type=ParticipantType,
                    default=ParticipantType.CarriedObject),
                locked_args={'target_type': PARTICIPANT_TYPE}),
            default='object_to_be_carried'),
        'owning_affordance':
        TunablePackSafeReference(
            description=
            '\n            The interaction that will be pushed that will own the carry\n            state (e.g. a put down).\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            allow_none=True),
        'carry_track_override':
        TunableVariant(
            description=
            '\n            Specify the carry track, instead of using the carry of the SI.\n            ',
            explicit=TrackOverrideExplicit.TunableFactory(),
            handedness=TrackOverrideHandedness.TunableFactory(),
            default='disabled',
            locked_args={'disabled': None})
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(*args, **kwargs)
        self.interaction = interaction
        self.sequence = sequence

    def _run(self, timeline):
        carry_track_override = self.carry_track_override.get_override(
            self.interaction, self.sim_participant_type
        ) if self.carry_track_override is not None else None
        target = self.target
        if target.target_type == EnterCarryWhileHolding.NONE:
            target_participant_type = None
        elif target.target_type == EnterCarryWhileHolding.OBJECT_TO_BE_CARRIED:
            target_participant_type = self.carry_obj_participant_type
        elif target.target_type == EnterCarryWhileHolding.PARTICIPANT_TYPE:
            target_participant_type = target.participant
        carry_element = enter_carry_while_holding(
            self.interaction,
            sequence=self.sequence,
            carry_obj_participant_type=self.carry_obj_participant_type,
            sim_participant_type=self.sim_participant_type,
            target_participant_type=target_participant_type,
            owning_affordance=self.owning_affordance,
            carry_track_override=carry_track_override)
        return timeline.run_child(carry_element)
コード例 #26
0
class OceanTuning:
    BEACH_LOCATOR_TAG = TunableTag(
        description=
        '\n        The tag we can use to get the beach locator definition.\n        '
    )
    OCEAN_DATA = TunableMapping(
        description=
        '\n        The species-age mapping to ocean data. This defines what\n        ages and species can wade in the water and what the water level\n        restrictions are as well as beach portal access objects.\n        ',
        key_name='species',
        key_type=TunableEnumEntry(
            description=
            '\n            The extended species that this data is for.\n            ',
            tunable_type=SpeciesExtended,
            default=SpeciesExtended.HUMAN),
        value_name='age_data',
        value_type=TunableList(
            description='\n            The ages and their data.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The ages and their ocean data.\n                ',
                ages=TunableEnumSet(
                    description=
                    '\n                    The age of the actor.\n                    ',
                    enum_type=Age),
                ocean_data=TunableTuple(
                    description=
                    '\n                    The ocean data for this Age.\n                    ',
                    wading_interval=TunableInterval(
                        description=
                        '\n                        The wading interval for Sims at this age and species. The lower\n                        bound indicates the minimum water height required to apply the\n                        wading walkstyle, and the upper bound indicates the maximum\n                        height we can walk into the water until we can potentially\n                        swim.\n                        ',
                        tunable_type=float,
                        default_lower=0.1,
                        default_upper=1.0,
                        minimum=0.01),
                    beach_portal_data=OptionalTunable(
                        description=
                        '\n                        An optional portal definition to allow sims to swim in\n                        the ocean. Without this, Sims at this age and species\n                        cannot swim in the ocean.\n                        ',
                        tunable=TunableReference(
                            description=
                            '\n                            The portals this age/species will use to swim in the ocean.\n                            ',
                            manager=services.snippet_manager(),
                            class_restrictions=('PortalData', ),
                            pack_safe=True)),
                    water_depth_error=TunableRange(
                        description=
                        '\n                        The error, in meters, that we allow for the swimming beach\n                        portals.\n                        ',
                        tunable_type=float,
                        default=0.05,
                        minimum=0.01),
                    swimwear_change_water_depth=TunableRange(
                        description=
                        "\n                        If a Sim's path includes water where the depth is at\n                        least the tuned value, in meters, they will switch into\n                        the outfit based on the outfit change reasonat the \n                        start of the path.\n                        ",
                        tunable_type=float,
                        default=0.1,
                        minimum=0),
                    swimwear_change_outfit_reason=OptionalTunable(
                        description=
                        '\n                        If enabled, the outfit change reason that determines which outfit\n                        category a Sim automatically changes into when \n                        entering water.\n                        ',
                        tunable=TunableEnumEntry(
                            tunable_type=OutfitChangeReason,
                            default=OutfitChangeReason.Invalid,
                            invalid_enums=(OutfitChangeReason.Invalid, )))))))
    beach_locator_definition = None

    @staticmethod
    def get_beach_locator_definition():
        if OceanTuning.beach_locator_definition is None:
            for definition in services.definition_manager(
            ).get_definitions_for_tags_gen((OceanTuning.BEACH_LOCATOR_TAG, )):
                OceanTuning.beach_locator_definition = definition
                break
        return OceanTuning.beach_locator_definition

    @staticmethod
    def get_actor_ocean_data(actor):
        if not actor.is_sim and not isinstance(actor, StubActor):
            return
        species_data = OceanTuning.OCEAN_DATA.get(actor.extended_species, None)
        if species_data is None:
            return
        actor_age = actor.age
        for age_data in species_data:
            if actor_age in age_data.ages:
                return age_data.ocean_data

    @staticmethod
    def get_actor_wading_interval(actor):
        ocean_data = OceanTuning.get_actor_ocean_data(actor)
        if ocean_data is not None:
            return ocean_data.wading_interval
        else:
            interval_actor = actor
            if not isinstance(actor, StubActor):
                if actor.vehicle_component is not None:
                    drivers = actor.get_users(sims_only=True)
                    for driver in drivers:
                        if driver.posture.is_vehicle:
                            if driver.posture.target is actor:
                                interval_actor = driver
                                break
            ocean_data = OceanTuning.get_actor_ocean_data(interval_actor)
            if ocean_data is not None:
                return ocean_data.wading_interval

    @staticmethod
    def get_actor_swimwear_change_info(actor):
        ocean_data = OceanTuning.get_actor_ocean_data(actor)
        if ocean_data is not None:
            return (ocean_data.swimwear_change_water_depth,
                    ocean_data.swimwear_change_outfit_reason)
        return (None, None)

    @staticmethod
    def make_depth_bounds_safe_for_surface_and_sim(routing_surface,
                                                   sim,
                                                   min_water_depth=None,
                                                   max_water_depth=None):
        interval = OceanTuning.get_actor_wading_interval(sim)
        return OceanTuning.make_depth_bounds_safe_for_surface(
            routing_surface,
            wading_interval=interval,
            min_water_depth=min_water_depth,
            max_water_depth=max_water_depth)

    @staticmethod
    def make_depth_bounds_safe_for_surface(routing_surface,
                                           wading_interval=None,
                                           min_water_depth=None,
                                           max_water_depth=None):
        if routing_surface.type == SurfaceType.SURFACETYPE_WORLD:
            surface_min_water_depth = min_water_depth
            if wading_interval is not None:
                if max_water_depth is None:
                    surface_max_water_depth = wading_interval.upper_bound
                else:
                    surface_max_water_depth = min(wading_interval.upper_bound,
                                                  max_water_depth)
                    surface_max_water_depth = 0
            else:
                surface_max_water_depth = 0
        elif routing_surface.type == SurfaceType.SURFACETYPE_POOL:
            if wading_interval is not None:
                if min_water_depth is None:
                    surface_min_water_depth = wading_interval.upper_bound
                else:
                    surface_min_water_depth = max(wading_interval.upper_bound,
                                                  min_water_depth)
            else:
                surface_min_water_depth = min_water_depth
            surface_max_water_depth = max_water_depth
        else:
            surface_min_water_depth = min_water_depth
            surface_max_water_depth = max_water_depth
        return (surface_min_water_depth, surface_max_water_depth)
コード例 #27
0
class AdoptionPickerSuperInteraction(SuperInteraction, PickerSuperInteractionMixin):
    INSTANCE_TUNABLES = {'picker_dialog': TunablePickerDialogVariant(description='\n            The Sim picker to use to show Sims eligible for adoption.\n            ', available_picker_flags=ObjectPickerTuningFlags.SIM, tuning_group=GroupNames.PICKERTUNING), 'picker_entries': TunableList(description='\n            A list of picker entries. For each Sim type (age/gender\n            combination), specify the ideal number of Sims in the picker.\n            ', tunable=TunableTuple(count=TunableInterval(description='\n                    Define the number of Sims that must match the specified\n                    creation data. The lower bound is the minimum required\n                    number. The upper bound is the ideal number.\n                    ', tunable_type=int, default_lower=1, default_upper=2, minimum=1), creation_data=_AdoptionSimData.TunableFactory()), tuning_group=GroupNames.PICKERTUNING), 'adoption_action': TunableVariant(description='\n            Define how the actual adoption is carried out.\n            ', continuation=_AdoptionActionPushContinuation.TunableFactory(), situation=_AdoptionActionStartSituation.TunableFactory(), default='continuation', tuning_group=GroupNames.PICKERTUNING)}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, choice_enumeration_strategy=SimPickerEnumerationStrategy(), **kwargs)
        self._picked_sim_ids = ()

    def _run_interaction_gen(self, timeline):
        self._show_picker_dialog(self.sim, target_sim=self.sim, target=self.target)
        yield from element_utils.run_child(timeline, element_utils.soft_sleep_forever())
        if not self._picked_sim_ids:
            return False
            yield
        self.adoption_action(self, self._picked_sim_ids)
        return True
        yield

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        adoption_service = services.get_adoption_service()
        with adoption_service.real_sim_info_cache():
            for entry in inst_or_cls.picker_entries:
                for sim_info in adoption_service.get_sim_infos(entry.count, entry.creation_data.age, entry.creation_data.gender, entry.creation_data.species):
                    aging_data = AgingTuning.get_aging_data(sim_info.species)
                    age_transition_data = aging_data.get_age_transition_data(sim_info.age)
                    row_description = age_transition_data.age_trait.display_name(sim_info)
                    yield SimPickerRow(sim_id=sim_info.sim_id, tag=sim_info.sim_id, row_description=row_description)

    def _pre_perform(self, *args, **kwargs):
        if self.sim.household.free_slot_count == 0:
            self.cancel(FinishingType.FAILED_TESTS, cancel_reason_msg="There aren't any free household slots.")
            return
        return super()._pre_perform(*args, **kwargs)

    def _on_selected(self, picked_sim_ids):
        self._picked_sim_ids = picked_sim_ids
        household = self.sim.household
        count = len(self._picked_sim_ids)
        if count > household.free_slot_count:
            self._picked_sim_ids = ()
        if self._picked_sim_ids:
            self.add_liability(AdoptionLiability.LIABILITY_TOKEN, AdoptionLiability(household, self._picked_sim_ids))

    def on_multi_choice_selected(self, picked_sim_ids, **kwargs):
        self._on_selected(picked_sim_ids)
        self.trigger_soft_stop()

    def on_choice_selected(self, picked_sim_id, **kwargs):
        if picked_sim_id is not None:
            self._on_selected((picked_sim_id,))
        self.trigger_soft_stop()
コード例 #28
0
class ProtestSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'number_of_protesters':
        TunableInterval(
            description=
            '\n                The number of other protesters to bring to the situation.\n                \n                This is an inclusive min/max range.\n                ',
            tunable_type=float,
            minimum=1,
            default_lower=3,
            default_upper=5,
            tuning_group=GroupNames.SITUATION),
        'protester_job':
        SituationJob.TunablePackSafeReference(
            description=
            '\n                The SituationJob for the Protester.\n                ',
            tuning_group=GroupNames.SITUATION),
        'protester_role':
        RoleState.TunablePackSafeReference(
            description=
            '\n                The SituationRole for the Protester.\n                ',
            tuning_group=GroupNames.SITUATION),
        'protester_search_filter':
        DynamicSimFilter.TunablePackSafeReference(
            description=
            '\n                Sim filter used to find sims or conform them into protesters.\n                We will select the cause for the protesters at runtime \n                from the specified weighted causes list below.\n                ',
            tuning_group=GroupNames.SITUATION),
        'protesting_situation_state':
        _ProtestingState.TunableFactory(
            description=
            '\n                The protest state.  Interactions of interest should be set \n                to interactions that may be run in order to end the situation.\n                ',
            tuning_group=GroupNames.SITUATION),
        'protestables':
        TunableList(
            description=
            '\n            List of possible protests and the signs for them.\n            These will be picked from based off the cause\n            ',
            tunable=TunableTuple(
                description=
                '\n                A protestable.  It is a cause and the sign to use for the cause.\n                ',
                sign_definition=TunableReference(
                    description=
                    '\n                    The definition of a protester flag.\n                    ',
                    manager=services.definition_manager()),
                cause=Trait.TunableReference(
                    description=
                    '\n                    The trait associated with this flag.\n                    ',
                    pack_safe=True)),
            tuning_group=GroupNames.SITUATION),
        'weighted_causes':
        TunableList(
            description=
            '\n            A weighted list of causes to choose for the protest.  We will pick\n            a random cause from this list as the subject of the protest.\n            ',
            tunable=TunableTuple(cause=Trait.TunablePackSafeReference(
                description=
                '\n                    The cause that this protest will promote/protest.\n                    '
            ),
                                 weight=Tunable(tunable_type=int, default=1)),
            tuning_group=GroupNames.SITUATION)
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        return (SituationStateData(1,
                                   _ProtestingState,
                                   factory=cls.protesting_situation_state), )

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.protester_job, cls.protester_role)]

    @classmethod
    def get_predefined_guest_list(cls):
        weighted_causes = tuple(
            (item.weight, item.cause) for item in cls.weighted_causes)
        cause = sims4.random.weighted_random_item(weighted_causes)
        protester_filter = cls.protester_search_filter(
            filter_terms=(TraitFilterTerm(invert_score=False,
                                          minimum_filter_score=0,
                                          trait=cause,
                                          ignore_if_wrong_pack=False), ))
        num_protesters_to_request = cls.number_of_protesters.random_int()
        instanced_sim_ids = [
            sim.sim_info.id
            for sim in services.sim_info_manager().instanced_sims_gen()
        ]
        household_sim_ids = [
            sim_info.id
            for sim_info in services.active_household().sim_info_gen()
        ]
        situation_manager = services.get_zone_situation_manager()
        global_auto_fill_blacklist = situation_manager.get_auto_fill_blacklist(
        )
        blacklist_sim_ids = set(
            itertools.chain(instanced_sim_ids, household_sim_ids,
                            global_auto_fill_blacklist))
        protester_results = services.sim_filter_service(
        ).submit_matching_filter(
            sim_filter=protester_filter,
            callback=None,
            allow_yielding=False,
            number_of_sims_to_find=num_protesters_to_request,
            blacklist_sim_ids=blacklist_sim_ids,
            gsi_source_fn=cls.get_sim_filter_gsi_name)
        if not protester_results:
            return
        guest_list = SituationGuestList(invite_only=True)
        for result in protester_results:
            guest_list.add_guest_info(
                SituationGuestInfo(result.sim_info.sim_id, cls.protester_job,
                                   RequestSpawningOption.MUST_SPAWN,
                                   BouncerRequestPriority.BACKGROUND_LOW))
        return guest_list

    def start_situation(self):
        super().start_situation()
        protestable = self.find_protestable_using_guest_list()
        initial_state = self.protesting_situation_state()
        if protestable:
            initial_state.set_sign_definition(protestable.sign_definition)
        self._change_state(initial_state)

    def _choose_protestable_from_sim(self, sim):
        possible_protests = [
            protestable for protestable in self.protestables
            if sim.has_trait(protestable.cause)
        ]
        if not possible_protests:
            return
        return random.choice(possible_protests)

    def find_protestable_using_guest_list(self):
        for guest in self._guest_list.get_guest_infos_for_job(
                self.protester_job):
            sim_info = services.sim_info_manager().get(guest.sim_id)
            if sim_info is not None:
                return self._choose_protestable_from_sim(sim_info)
コード例 #29
0
class InventoryItemComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=types.INVENTORY_ITEM_COMPONENT, persistence_key=protocols.PersistenceMaster.PersistableData.InventoryItemComponent):
    __qualname__ = 'InventoryItemComponent'
    DEFAULT_ADD_TO_WORLD_AFFORDANCES = TunableList(description="\n        A list of default affordances to add objects in a Sim's inventory to\n        the world.\n        ", tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)))
    DEFAULT_ADD_TO_SIM_INVENTORY_AFFORDANCES = TunableList(description="\n        A list of default affordances to add objects to a Sim's inventory.\n        ", tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)))
    DEFAULT_NO_CARRY_ADD_TO_WORLD_AFFORDANCES = TunableList(description="\n        A list of default affordances to add objects in a Sim's inventory that\n        skip the carry pose to the world.\n        ", tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)))
    DEFAULT_NO_CARRY_ADD_TO_SIM_INVENTORY_AFFORDANCES = TunableList(description="\n        A list of default affordances to add objects that skip the carry pose\n        to a Sim's inventory.\n        ", tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)))
    PUT_AWAY_AFFORDANCE = TunableReference(description='\n        An affordance for putting an object away in an inventory.\n        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))
    STACK_SORT_ORDER_STATES = TunableList(description='\n        A list of states that dictate the order of an inventory stack. States\n        lower down in this list will cause the object to be further down in\n        the stack.\n        ', tunable=TunableTuple(description='\n            States to consider.\n            ', state=TunableReference(description='\n                State to sort on.\n                ', manager=services.get_instance_manager(sims4.resources.Types.OBJECT_STATE), class_restrictions='ObjectState'), is_value_order_inverted=Tunable(description='\n                Normally, higher state value is better. For example, an\n                IngredientQuality value of 0 is the worst and 10 is the best.\n\n                However, there are some state values where lower is better,\n                e.g. burnt state is tied to the burnt commodity where 0 is\n                unburnt and 100 is completely burnt. This option should be set\n                for these states.\n                ', tunable_type=bool, default=False)))

    @staticmethod
    def _verify_tunable_callback(cls, tunable_name, source, valid_inventory_types, skip_carry_pose, inventory_only, **kwargs):
        if InventoryType.UNDEFINED in valid_inventory_types:
            logger.error('Inventory Item is not valid for any inventories.  Please remove the component or update your tuning.', owner='mduke')
        if skip_carry_pose:
            for inv_type in valid_inventory_types:
                inv_data = InventoryTypeTuning.INVENTORY_TYPE_DATA.get(inv_type)
                while inv_data is not None and not inv_data.skip_carry_pose_allowed:
                    logger.error('You cannot tune your item to skip carry\n                    pose unless it is only valid for the sim, mailbox, and/or\n                    hidden inventories.  Any other inventory type will not\n                    properly support this option. -Mike Duke')

    FACTORY_TUNABLES = {'description': '\n            An object with this component can be placed in inventories.\n            ', 'valid_inventory_types': TunableList(description='\n            A list of Inventory Types this object can go into.\n            ', tunable=TunableEnumEntry(description='\n                Any inventory type tuned here is one in which the owner of this\n                component can be placed into.\n                ', tunable_type=InventoryType, default=InventoryType.UNDEFINED)), 'skip_carry_pose': Tunable(description='\n            If Checked, this object will not use the normal pick up or put down\n            SI which goes through the carry pose.  It will instead use a swipe\n            pick up which does a radial route and swipe.  Put down will run a\n            FGL and do a swipe then fade in the object in the world. You can\n            only use this for an object that is only valid for the sim, hidden\n            and/or mailbox inventory.  It will not work with other inventory\n            types.', tunable_type=bool, default=False), 'inventory_only': Tunable(description='\n            Denote the owner of this component as an "Inventory Only" object.\n            These objects are not meant to be placed in world, and will not\n            generate any of the default interactions normally generated for\n            inventory objects.\n            ', tunable_type=bool, default=False), 'visible': Tunable(description="\n            Whether the object is visible in the Sim's Inventory or not.\n            Objects that are invisible won't show up but can still be tested\n            for.\n            ", tunable_type=bool, default=True), 'put_away_affordance': OptionalTunable(description='\n            Whether to use the default put away interaction or an overriding\n            one. The default affordance is tuned at\n            objects.components.inventory_item -> InventoryItemComponent -> Put\n            Away Affordance.\n            ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)), disabled_name='DEFAULT', enabled_name='OVERRIDE'), 'stack_scheme': TunableEnumEntry(description="\n            How object should stack in an inventory. If you're confused on\n            what definitions and variants are, consult a build/buy designer or\n            producer.\n            \n            NONE: Object will not stack.\n            \n            VARIANT_GROUP: This object will stack with objects with in the same\n            variant group. For example, orange guitars will stack with red\n            guitars.\n\n            DEFINITION: This object will stack with objects with the same\n            definition. For example, orange guitars will stack with other\n            orange guitars but not with red guitars.\n            ", tunable_type=StackScheme, default=StackScheme.VARIANT_GROUP), 'can_place_in_world': Tunable(description='\n            If checked, this object will generate affordances allowing it to be\n            placed in the world. If unchecked, it will not.\n            ', tunable_type=bool, default=True), 'verify_tunable_callback': _verify_tunable_callback}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._current_inventory_type = None
        self._last_inventory_owner_ref = None
        self._stack_count = 1
        self._stack_id = None
        self._sort_order = None

    def on_state_changed(self, state, old_value, new_value):
        inventory = self.get_inventory()
        if inventory is not None and not inventory.owner.is_sim:
            inventory.object_state_update_callback(old_value, new_value)
        for state_info in InventoryItemComponent.STACK_SORT_ORDER_STATES:
            while state_info.state is state:
                self._sort_order = None
                if inventory is not None:
                    inventory.push_inventory_item_update_msg(self.owner)
                return

    def post_component_reset(self):
        inventory = self.get_inventory()
        if inventory is not None:
            inventory.push_inventory_item_update_msg(self.owner)

    @property
    def current_inventory_type(self):
        return self._current_inventory_type

    @property
    def _last_inventory_owner(self):
        if self._last_inventory_owner_ref is not None:
            return self._last_inventory_owner_ref()

    @_last_inventory_owner.setter
    def _last_inventory_owner(self, value):
        if value is None:
            self._last_inventory_owner_ref = None
        else:
            self._last_inventory_owner_ref = value.ref()

    @componentmethod_with_fallback(lambda : False)
    def is_in_inventory(self):
        return self._current_inventory_type is not None

    @componentmethod_with_fallback(lambda : 1)
    def stack_count(self):
        return self._stack_count

    @componentmethod_with_fallback(lambda count: None)
    def set_stack_count(self, count):
        self._stack_count = count

    @componentmethod_with_fallback(lambda num: None)
    def update_stack_count(self, num):
        pass

    @componentmethod_with_fallback(lambda sim=None: False)
    def is_in_sim_inventory(self, sim=None):
        if sim is not None:
            inventory = self.get_inventory()
            if inventory is not None:
                return inventory.owner is sim
            return False
        return self._current_inventory_type == InventoryType.SIM

    def on_added_to_inventory(self):
        inventory = self.get_inventory()
        if inventory is not None and inventory.owner.is_sim:
            services.get_event_manager().process_event(TestEvent.OnInventoryChanged, sim_info=inventory.owner.sim_info)

    def on_removed_from_inventory(self):
        owner = self._last_inventory_owner
        if owner.is_sim:
            services.get_event_manager().process_event(TestEvent.OnInventoryChanged, sim_info=owner.sim_info)
        inventory = owner.inventory_component
        if owner is not None and inventory is not None and inventory.inventory_type not in (InventoryType.MAILBOX, InventoryType.HIDDEN):
            self.owner.new_in_inventory = False

    @componentmethod_with_fallback(lambda : None)
    def get_inventory(self):
        if self.is_in_inventory():
            if self._last_inventory_owner is not None:
                return self._last_inventory_owner.inventory_component
            if self.is_in_sim_inventory():
                logger.error('Object exists but owning Sim does not!  This means we leaked the inventory item when the Sim was deleted.', owner='jpollak/mduke')
            return services.current_zone().lot.get_object_inventory(self._current_inventory_type)

    @componentmethod_with_fallback(lambda inventory_type: False)
    def can_go_in_inventory_type(self, inventory_type):
        if inventory_type == InventoryType.HIDDEN:
            if InventoryType.MAILBOX not in self.valid_inventory_types:
                logger.warn('Object can go in the hidden inventory, but not the mailbox: {}', self)
            return True
        return inventory_type in self.valid_inventory_types

    def get_stack_id(self):
        if self._stack_id is None:
            self._stack_id = services.inventory_manager().get_stack_id(self.owner, self.stack_scheme)
        return self._stack_id

    @componentmethod_with_fallback(lambda *args, **kwargs: 0)
    def get_stack_sort_order(self, inspect_only=False):
        if not inspect_only and self._sort_order is None:
            self._recalculate_sort_order()
        if self._sort_order is not None:
            return self._sort_order
        return 0

    def _recalculate_sort_order(self):
        sort_order = 0
        multiplier = 1
        for state_info in InventoryItemComponent.STACK_SORT_ORDER_STATES:
            state = state_info.state
            if state is None:
                pass
            invert_order = state_info.is_value_order_inverted
            num_values = len(state.values)
            if self.owner.has_state(state):
                state_value = self.owner.get_state(state)
                value = state.values.index(state_value)
                if not invert_order:
                    value = num_values - value - 1
                sort_order += multiplier*value
            multiplier *= num_values
        self._sort_order = sort_order

    def component_interactable_gen(self):
        if not self.inventory_only:
            yield self

    def component_super_affordances_gen(self, **kwargs):
        if self.owner.get_users():
            return
        if not self.inventory_only:
            lot = None
            obj_inventory_found = False
            for valid_type in self.valid_inventory_types:
                if valid_type == InventoryType.SIM:
                    if self.skip_carry_pose:
                        yield self.DEFAULT_NO_CARRY_ADD_TO_SIM_INVENTORY_AFFORDANCES
                        if self.can_place_in_world:
                            yield self.DEFAULT_NO_CARRY_ADD_TO_WORLD_AFFORDANCES
                            yield self.DEFAULT_ADD_TO_SIM_INVENTORY_AFFORDANCES
                            if self.can_place_in_world:
                                yield self.DEFAULT_ADD_TO_WORLD_AFFORDANCES
                                while not obj_inventory_found:
                                    if self.skip_carry_pose:
                                        pass
                                    if not lot:
                                        lot = services.current_zone().lot
                                    inventory = lot.get_object_inventory(valid_type)
                                    if inventory is not None and inventory.has_owning_object:
                                        inv_data = InventoryTypeTuning.INVENTORY_TYPE_DATA.get(valid_type)
                                        if inv_data is None or inv_data.put_away_allowed:
                                            obj_inventory_found = True
                                            if self.put_away_affordance is None:
                                                yield self.PUT_AWAY_AFFORDANCE
                                            else:
                                                yield self.put_away_affordance
                        else:
                            while not obj_inventory_found:
                                if self.skip_carry_pose:
                                    pass
                                if not lot:
                                    lot = services.current_zone().lot
                                inventory = lot.get_object_inventory(valid_type)
                                if inventory is not None and inventory.has_owning_object:
                                    inv_data = InventoryTypeTuning.INVENTORY_TYPE_DATA.get(valid_type)
                                    if inv_data is None or inv_data.put_away_allowed:
                                        obj_inventory_found = True
                                        if self.put_away_affordance is None:
                                            yield self.PUT_AWAY_AFFORDANCE
                                        else:
                                            yield self.put_away_affordance
                    else:
                        yield self.DEFAULT_ADD_TO_SIM_INVENTORY_AFFORDANCES
                        if self.can_place_in_world:
                            yield self.DEFAULT_ADD_TO_WORLD_AFFORDANCES
                            while not obj_inventory_found:
                                if self.skip_carry_pose:
                                    pass
                                if not lot:
                                    lot = services.current_zone().lot
                                inventory = lot.get_object_inventory(valid_type)
                                if inventory is not None and inventory.has_owning_object:
                                    inv_data = InventoryTypeTuning.INVENTORY_TYPE_DATA.get(valid_type)
                                    if inv_data is None or inv_data.put_away_allowed:
                                        obj_inventory_found = True
                                        if self.put_away_affordance is None:
                                            yield self.PUT_AWAY_AFFORDANCE
                                        else:
                                            yield self.put_away_affordance
                else:
                    while not obj_inventory_found:
                        if self.skip_carry_pose:
                            pass
                        if not lot:
                            lot = services.current_zone().lot
                        inventory = lot.get_object_inventory(valid_type)
                        if inventory is not None and inventory.has_owning_object:
                            inv_data = InventoryTypeTuning.INVENTORY_TYPE_DATA.get(valid_type)
                            if inv_data is None or inv_data.put_away_allowed:
                                obj_inventory_found = True
                                if self.put_away_affordance is None:
                                    yield self.PUT_AWAY_AFFORDANCE
                                else:
                                    yield self.put_away_affordance

    def valid_object_inventory_gen(self):
        lot = services.current_zone().lot
        for valid_type in self.valid_inventory_types:
            while valid_type != InventoryType.SIM:
                inventory = lot.get_object_inventory(valid_type)
                if inventory is not None:
                    while True:
                        for obj in inventory.owning_objects_gen():
                            yield obj

    def set_inventory_type(self, inventory_type, owner):
        if inventory_type == self._current_inventory_type and owner == self._last_inventory_owner:
            return
        if self._current_inventory_type != None:
            current_inventory = self.get_inventory()
            self._remove_inventory_effects(current_inventory)
        if inventory_type is None:
            self._current_inventory_type = None
        else:
            if inventory_type == InventoryType.SIM:
                pass
            self._current_inventory_type = inventory_type
            self._last_inventory_owner = owner
            current_inventory = self.get_inventory()
            self._apply_inventory_effects(current_inventory)

    @property
    def previous_inventory(self):
        if self._current_inventory_type is not None:
            return
        if self._last_inventory_owner is not None:
            return self._last_inventory_owner.inventory_component

    def clear_previous_inventory(self):
        self._last_inventory_owner = None

    def save(self, persistence_master_message):
        persistable_data = protocols.PersistenceMaster.PersistableData()
        persistable_data.type = protocols.PersistenceMaster.PersistableData.InventoryItemComponent
        inventory_item_save = persistable_data.Extensions[protocols.PersistableInventoryItemComponent.persistable_data]
        inventory_item_save.inventory_type = self._current_inventory_type if self._current_inventory_type is not None else 0
        inventory_item_save.owner_id = self._last_inventory_owner.id if self._last_inventory_owner is not None else 0
        inventory_item_save.stack_count = self._stack_count
        persistence_master_message.data.extend([persistable_data])

    def load(self, message):
        data = message.Extensions[protocols.PersistableInventoryItemComponent.persistable_data]
        if data.owner_id != 0:
            self._last_inventory_owner = services.object_manager().get(data.owner_id)
        else:
            self._last_inventory_owner = None
        if data.inventory_type == 0:
            return
        if data.inventory_type in InventoryType.values:
            self._current_inventory_type = InventoryType(data.inventory_type)
            if self._current_inventory_type in UNIQUE_OBJECT_INVENTORY_TYPES:
                inv = self._last_inventory_owner.inventory_component if self._last_inventory_owner is not None else None
                logger.assert_log(inv is not None, 'Loading object {} in a unique object inventory but missing inventory owner object.', self.owner, owner='tingyul')
            else:
                lot = services.current_zone().lot
                inv = lot.get_object_inventory(self._current_inventory_type)
                if inv is None:
                    inv = lot.create_object_inventory(self._current_inventory_type)
                inv._insert_item(self.owner, use_overflow=False, call_add=False, object_with_inventory=self._last_inventory_owner, try_find_matching_item=False)
            self._apply_inventory_effects(inv)
        self._stack_count = data.stack_count

    def _apply_inventory_effects(self, inventory):
        effects = inventory.gameplay_effects
        if effects:
            for (stat_type, decay_modifier) in effects.decay_modifiers.items():
                tracker = self.owner.get_tracker(stat_type)
                while tracker is not None:
                    stat = tracker.get_statistic(stat_type)
                    if stat is not None:
                        stat.add_decay_rate_modifier(decay_modifier)

    def _remove_inventory_effects(self, inventory):
        effects = inventory.gameplay_effects
        if effects:
            for (stat_type, decay_modifier) in effects.decay_modifiers.items():
                tracker = self.owner.get_tracker(stat_type)
                while tracker is not None:
                    stat = tracker.get_statistic(stat_type)
                    if stat is not None:
                        stat.remove_decay_rate_modifier(decay_modifier)
コード例 #30
0
class TutorialTip(
        metaclass=sims4.tuning.instances.HashedTunedInstanceMetaclass,
        manager=services.get_instance_manager(
            sims4.resources.Types.TUTORIAL_TIP)):
    INSTANCE_TUNABLES = {
        'required_tip_groups':
        TunableList(
            description=
            '\n            The Tip Groups that must be complete for this tip to be valid.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL_TIP),
                                     class_restrictions='TutorialTipGroup'),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_ui_list':
        TunableList(
            description=
            '\n            The UI elements that are required to be present in order for this\n            tutorial tip to be valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=TutorialTipUiElement,
                                     default=TutorialTipUiElement.UI_INVALID),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_ui_hidden_list':
        TunableList(
            description=
            '\n            The UI elements that are required to NOT be present in order for this\n            tutorial tip to be valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=TutorialTipUiElement,
                                     default=TutorialTipUiElement.UI_INVALID),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_game_state':
        TunableEnumEntry(
            description=
            '\n            The state the game must be in for this tutorial tip to be valid.\n            ',
            tunable_type=TutorialTipGameState,
            default=TutorialTipGameState.GAMESTATE_NONE,
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_tips_not_satisfied':
        TunableList(
            description=
            '\n            This is a list of tips that must be un-satisfied in order for this\n            tip to activate. If any tip in this list is satisfied, this tip will\n            not activate.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL_TIP),
                                     class_restrictions='TutorialTip'),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'platform_filter':
        TunableEnumEntry(
            description=
            '\n            The platforms on which this tutorial tip is shown.\n            ',
            tunable_type=tutorials.tutorial.TutorialPlatformFilter,
            default=tutorials.tutorial.TutorialPlatformFilter.ALL_PLATFORMS,
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_tutorial_mode':
        TunableEnumEntry(
            description=
            '\n            What mode this tutorial tip should be restricted to.\n            STANDARD allows this tip to be in the original / standard tutorial mode.\n            FTUE allows this tip to be in the FTUE tutorial mode.\n            DISABLED means this tip is valid in any mode.\n            ',
            tunable_type=TutorialMode,
            default=TutorialMode.STANDARD,
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'display':
        TunableTutorialTipDisplay(
            description=
            '\n            This display information for this tutorial tip.\n            ',
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'display_narration':
        OptionalTunable(
            description=
            '\n            Optionally play narration voice-over and display subtitles.\n            ',
            tunable=TunableTuple(
                voiceover_audio=TunableResourceKey(
                    description=
                    '\n                    Narration audio to play.\n                    ',
                    default=None,
                    allow_none=True,
                    resource_types=(sims4.resources.Types.PROPX, )),
                voiceover_audio_ps4=TunableResourceKey(
                    description=
                    '\n                    Narration audio to play specific to PS4.\n                    ',
                    default=None,
                    allow_none=True,
                    resource_types=(sims4.resources.Types.PROPX, )),
                voiceover_audio_xb1=TunableResourceKey(
                    description=
                    '\n                    Narration audio to play specific to XB1.\n                    ',
                    default=None,
                    allow_none=True,
                    resource_types=(sims4.resources.Types.PROPX, )),
                subtitle_text=TunableLocalizedString(
                    description=
                    '\n                    Subtitles to display while audio narration is playing.\n                    '
                ),
                subtitle_display_location=TunableVariant(
                    description=
                    '\n                    What area on the screen the subtitles should appear.\n                    Top    - Use the generic top-of-screen position.\n                    Bottom - Use the generic bottom-of-screen position.\n                    Custom - Specify a custom position in terms of % vertically.\n                    ',
                    location=TunableEnumEntry(
                        description=
                        '\n                        Semantic location (UX-defined) for where the subtitles should appear.\n                        ',
                        tunable_type=TutorialTipSubtitleDisplayLocation,
                        default=TutorialTipSubtitleDisplayLocation.BOTTOM),
                    custom=TunablePercent(
                        description=
                        '\n                        Vertical position for the subtitles, expressed as a\n                        percentage of the height of the screen.\n                        ',
                        default=90),
                    default='location'),
                satisfy_when_voiceover_finished=Tunable(
                    description=
                    '\n                    If set, the tutorial tip will be marked as satisfied when the\n                    voiceover completes or is interrupted.\n                    ',
                    tunable_type=bool,
                    default=False),
                delay_satisfaction_until_voiceover_finished=Tunable(
                    description=
                    '\n                    If set, the tutorial tip will not be marked satisfied until after\n                    the voiceover completes, preventing the voiceover from being\n                    interrupted by external satisfaction.\n                    ',
                    tunable_type=bool,
                    default=False),
                keep_subtitle_visible_until_satisfaction=Tunable(
                    description=
                    '\n                    If set, the subtitle will remain visible until the tutorial tip is\n                    marked as satisfied, even though the voiceover may have finished.\n                    ',
                    tunable_type=bool,
                    default=False),
                export_class_name='TutorialTipNarrationDisplay'),
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'activation_ui_message':
        TunableTutorialTipUiMessage(
            description=
            '\n            Sends a message to the UI when this tip is activated.\n            ',
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'deactivation_ui_message':
        TunableTutorialTipUiMessage(
            description=
            '\n            Sends a message to the UI when this tip is deactivated.\n            ',
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'buffs':
        TunableList(
            description=
            '\n            Buffs that will be applied at the start of this tutorial tip.\n            ',
            tunable=TunableBuffReference(),
            tuning_group=GROUP_NAME_ACTIONS),
        'buffs_removed_on_deactivate':
        Tunable(
            description=
            '\n            If enabled, this tip will remove those buffs on deactivate.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'commodities_to_solve':
        TunableSet(
            description=
            "\n            A set of commodities we will attempt to solve. This will result in\n            the Sim's interaction queue being filled with various interactions.\n            ",
            tunable=TunableReference(services.statistic_manager()),
            tuning_group=GROUP_NAME_ACTIONS),
        'gameplay_loots':
        OptionalTunable(
            description=
            '\n            Loots that will be given at the start of this tip.\n            Actor is is the sim specified by Sim Actor.\n            Target is the sim specified by Sim Target.\n            ',
            tunable=TunableList(
                tunable=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.ACTION),
                                         class_restrictions=('LootActions', ),
                                         pack_safe=True)),
            tuning_group=GROUP_NAME_ACTIONS),
        'restricted_affordances':
        OptionalTunable(
            description=
            '\n            If enabled, use the filter to determine which affordances are allowed.\n            ',
            tunable=TunableTuple(
                visible_affordances=TunableAffordanceFilterSnippet(
                    description=
                    '\n                    The filter of affordances that are visible.\n                    '
                ),
                tooltip=OptionalTunable(
                    description=
                    '\n                    Tooltip when interaction is disabled by tutorial restrictions\n                    If not specified, will use the default in the tutorial service\n                    tuning.\n                    ',
                    tunable=sims4.localization.TunableLocalizedStringFactory(
                    )),
                enabled_affordances=TunableAffordanceFilterSnippet(
                    description=
                    '\n                    The filter of visible affordances that are enabled.\n                    '
                )),
            tuning_group=GROUP_NAME_ACTIONS),
        'call_to_actions':
        OptionalTunable(
            description=
            '\n            Call to actions that should persist for the duration of this tip.\n            ',
            tunable=TunableList(
                tunable=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.CALL_TO_ACTION),
                                         pack_safe=True)),
            tuning_group=GROUP_NAME_ACTIONS),
        'end_drama_node':
        Tunable(
            description=
            '\n            If enabled, this tip will end the tutorial drama node.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'sim_actor':
        TunableEnumEntry(
            description=
            "\n            The entity who will be the actor sim for loot, and will\n            receive the items that aren't specified via loots.\n            \n            If there is no Tutorial Drama Node active, actor will be active\n            sim\n            ",
            tunable_type=TutorialTipActorOption,
            default=TutorialTipActorOption.ACTIVE_SIM,
            tuning_group=GROUP_NAME_ACTIONS),
        'sim_target':
        TunableEnumEntry(
            description=
            '\n            The entity who will be the target sim for loot\n            \n            If there is no Tutorial Drama Node active, target sim will be active\n            sim.\n            ',
            tunable_type=TutorialTipActorOption,
            default=TutorialTipActorOption.ACTIVE_SIM,
            tuning_group=GROUP_NAME_ACTIONS),
        'add_target_to_actor_household':
        Tunable(
            description=
            '\n            If enabled, target sim will be added to active sim household.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'make_housemate_unselectable':
        Tunable(
            description=
            '\n            If enabled, housemate will be unselectable for the duration of the\n            tooltip.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'timeout_satisfies':
        Tunable(
            description=
            '\n            If enabled, this tip is satisfied when the timeout is reached.\n            If disabled, this tip will not satisfy when the timeout is reached.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.ClientBinary),
        'gameplay_test':
        OptionalTunable(
            description=
            '\n            Tests that, if passed, will satisfy this tutorial tip.\n            Only one test needs to pass to satisfy. These are intended for tips\n            where the satisfy message should be tested and sent at a later time.\n            ',
            tunable=tutorials.tutorial.TunableTutorialTestVariant(),
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.All),
        'sim_tested':
        TunableEnumEntry(
            description=
            '\n            The entity who must fulfill the test events.\n            \n            If there is no Tutorial Drama Node, player sim and housemate sim will be active\n            sim.\n            ',
            tunable_type=TutorialTipTestSpecificityOption,
            default=TutorialTipTestSpecificityOption.UNSPECIFIED,
            tuning_group=GROUP_NAME_SATISFY),
        'time_of_day':
        OptionalTunable(
            description=
            '\n            If specified, tutorialtip will be satisfied once the time passes \n            the specified time.\n            ',
            tunable=TunableTimeOfDay(),
            tuning_group=GROUP_NAME_SATISFY),
        'gameplay_immediate_test':
        OptionalTunable(
            description=
            '\n            Tests that, if passed, will satisfy this tutorial tip.\n            Only one test needs to pass to satisfy. These are intended for tips\n            where the satisfy message should be tested and sent back immediately.\n            ',
            tunable=tutorials.tutorial.TunableTutorialTestVariant(),
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.All),
        'satisfy_on_active_sim_change':
        Tunable(
            description=
            '\n            If enabled, this tip is satisfied when the active sim changes\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.All),
        'satisfy_on_activate':
        Tunable(
            description=
            "\n            If enabled, this tip is satisfied immediately when all of it's\n            preconditions have been met.\n            ",
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.ClientBinary),
        'tutorial_group_to_complete_on_skip':
        TunableReference(
            description=
            '\n            The tutorial group who will have all tutorial tips within it\n            completed when the button to skip all is pressed from this tip.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL_TIP),
            class_restrictions='TutorialTipGroup',
            export_modes=ExportModes.ClientBinary)
    }

    def __init__(self):
        raise NotImplementedError

    @classmethod
    def activate(cls):
        tutorial_service = services.get_tutorial_service()
        client = services.client_manager().get_first_client()
        actor_sim_info = client.active_sim.sim_info
        target_sim_info = actor_sim_info
        housemate_sim_info = None
        tutorial_drama_node = None
        drama_scheduler = services.drama_scheduler_service()
        if drama_scheduler is not None:
            drama_nodes = drama_scheduler.get_running_nodes_by_drama_node_type(
                DramaNodeType.TUTORIAL)
            if drama_nodes:
                tutorial_drama_node = drama_nodes[0]
                housemate_sim_info = tutorial_drama_node.get_housemate_sim_info(
                )
                player_sim_info = tutorial_drama_node.get_player_sim_info()
                if cls.sim_actor == TutorialTipActorOption.PLAYER_SIM:
                    actor_sim_info = player_sim_info
                elif cls.sim_actor == TutorialTipActorOption.HOUSEMATE_SIM:
                    actor_sim_info = housemate_sim_info
                if cls.sim_target == TutorialTipActorOption.PLAYER_SIM:
                    target_sim_info = player_sim_info
                elif cls.sim_target == TutorialTipActorOption.HOUSEMATE_SIM:
                    target_sim_info = housemate_sim_info
        if cls.gameplay_immediate_test is not None:
            resolver = event_testing.resolver.SingleSimResolver(actor_sim_info)
            if resolver(cls.gameplay_immediate_test):
                cls.satisfy()
            else:
                return
        for buff_ref in cls.buffs:
            actor_sim_info.add_buff_from_op(buff_ref.buff_type,
                                            buff_reason=buff_ref.buff_reason)
        if cls.gameplay_test is not None:
            services.get_event_manager().register_tests(
                cls, [cls.gameplay_test])
        if cls.satisfy_on_active_sim_change:
            client = services.client_manager().get_first_client()
            if client is not None:
                client.register_active_sim_changed(cls._on_active_sim_change)
        if cls.commodities_to_solve:
            actor_sim = actor_sim_info.get_sim_instance()
            if actor_sim is not None:
                context = InteractionContext(
                    actor_sim,
                    InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT,
                    priority.Priority.High,
                    bucket=InteractionBucketType.DEFAULT)
                for commodity in cls.commodities_to_solve:
                    if not actor_sim.queue.can_queue_visible_interaction():
                        break
                    autonomy_request = autonomy.autonomy_request.AutonomyRequest(
                        actor_sim,
                        autonomy_mode=autonomy.autonomy_modes.FullAutonomy,
                        commodity_list=(commodity, ),
                        context=context,
                        consider_scores_of_zero=True,
                        posture_behavior=AutonomyPostureBehavior.
                        IGNORE_SI_STATE,
                        distance_estimation_behavior=
                        AutonomyDistanceEstimationBehavior.
                        ALLOW_UNREACHABLE_LOCATIONS,
                        allow_opportunity_cost=False,
                        autonomy_mode_label_override='Tutorial')
                    selected_interaction = services.autonomy_service(
                    ).find_best_action(autonomy_request)
                    AffordanceObjectPair.execute_interaction(
                        selected_interaction)
        if cls.gameplay_loots:
            resolver = DoubleSimResolver(actor_sim_info, target_sim_info)
            for loot_action in cls.gameplay_loots:
                loot_action.apply_to_resolver(resolver)
        if cls.restricted_affordances is not None and tutorial_service is not None:
            tutorial_service.set_restricted_affordances(
                cls.restricted_affordances.visible_affordances,
                cls.restricted_affordances.tooltip,
                cls.restricted_affordances.enabled_affordances)
        if cls.call_to_actions is not None:
            call_to_action_service = services.call_to_action_service()
            for call_to_action_fact in cls.call_to_actions:
                call_to_action_service.begin(call_to_action_fact, None)
        if cls.add_target_to_actor_household:
            household_manager = services.household_manager()
            household_manager.switch_sim_household(target_sim_info)
        if cls.make_housemate_unselectable and tutorial_service is not None:
            tutorial_service.set_unselectable_sim(housemate_sim_info)
        if cls.end_drama_node and tutorial_drama_node is not None:
            tutorial_drama_node.end()
        if cls.time_of_day is not None and tutorial_service is not None:
            tutorial_service.add_tutorial_alarm(cls, lambda _: cls.satisfy(),
                                                cls.time_of_day)

    @classmethod
    def _on_active_sim_change(cls, old_sim, new_sim):
        cls.satisfy()

    @classmethod
    def handle_event(cls, sim_info, event, resolver):
        if cls.gameplay_test is not None and resolver(cls.gameplay_test):
            if cls.sim_tested != TutorialTipTestSpecificityOption.UNSPECIFIED:
                client = services.client_manager().get_first_client()
                test_sim_info = client.active_sim.sim_info
                drama_scheduler = services.drama_scheduler_service()
                if drama_scheduler is not None:
                    drama_nodes = drama_scheduler.get_running_nodes_by_drama_node_type(
                        DramaNodeType.TUTORIAL)
                    if drama_nodes:
                        drama_node = drama_nodes[0]
                        if cls.sim_tested == TutorialTipTestSpecificityOption.PLAYER_SIM:
                            test_sim_info = drama_node.get_player_sim_info()
                        elif cls.sim_tested == TutorialTipTestSpecificityOption.HOUSEMATE_SIM:
                            test_sim_info = drama_node.get_housemate_sim_info()
                if test_sim_info is not sim_info:
                    return
            cls.satisfy()

    @classmethod
    def satisfy(cls):
        op = distributor.ops.SetTutorialTipSatisfy(cls.guid64)
        distributor_instance = Distributor.instance()
        distributor_instance.add_op_with_no_owner(op)

    @classmethod
    def deactivate(cls):
        tutorial_service = services.get_tutorial_service()
        client = services.client_manager().get_first_client()
        if cls.gameplay_test is not None:
            services.get_event_manager().unregister_tests(
                cls, (cls.gameplay_test, ))
        if cls.satisfy_on_active_sim_change and client is not None:
            client.unregister_active_sim_changed(cls._on_active_sim_change)
        if cls.restricted_affordances is not None and tutorial_service is not None:
            tutorial_service.clear_restricted_affordances()
        if cls.call_to_actions is not None:
            call_to_action_service = services.call_to_action_service()
            for call_to_action_fact in cls.call_to_actions:
                call_to_action_service.end(call_to_action_fact)
        if cls.buffs_removed_on_deactivate:
            actor_sim_info = None
            if client is not None:
                actor_sim_info = client.active_sim.sim_info
            drama_scheduler = services.drama_scheduler_service()
            if drama_scheduler is not None:
                drama_nodes = drama_scheduler.get_running_nodes_by_drama_node_type(
                    DramaNodeType.TUTORIAL)
                if drama_nodes:
                    tutorial_drama_node = drama_nodes[0]
                    if cls.sim_actor == TutorialTipActorOption.PLAYER_SIM:
                        actor_sim_info = tutorial_drama_node.get_player_sim_info(
                        )
                    elif cls.sim_actor == TutorialTipActorOption.HOUSEMATE_SIM:
                        actor_sim_info = tutorial_drama_node.get_housemate_sim_info(
                        )
            if actor_sim_info is not None:
                for buff_ref in cls.buffs:
                    actor_sim_info.remove_buff_by_type(buff_ref.buff_type)
        if cls.time_of_day is not None and tutorial_service is not None:
            tutorial_service.remove_tutorial_alarm(cls)
        if cls.make_housemate_unselectable and tutorial_service is not None:
            tutorial_service.set_unselectable_sim(None)