Esempio n. 1
0
 def __init__(self, **kwargs):
     super().__init__(
         affordance_list=TunableAffordanceFilterSnippet(),
         score=Tunable(
             int,
             1,
             description='score sim will receive if running affordance'))
Esempio n. 2
0
class ServicePrerollAutonomy(HasTunableFactory, AutoFactoryInit):
    __qualname__ = 'ServicePrerollAutonomy'
    FACTORY_TUNABLES = {
        'description':
        '\n        A tunable to specify tests/settings for how to post process a\n        manual autonomy request on a sim. EX: preroll autonomy for the maid\n        when she first gets onto the lot has an affordance link that\n        blacklists her from doing the serviceNpc_noMoreWork\n        interaction.',
        'super_affordance_compatibility':
        TunableAffordanceFilterSnippet(),
        'preroll_source':
        TunableEnumEntry(
            description=
            '\n            The source of the context of the autonomy roll.\n            Whether you want the autonomy roll to pretend like it was autonomy\n            that did the tests for available interactions or have the interaction\n            be tested and run as if it was user directed.\n            ',
            tunable_type=InteractionSource,
            default=InteractionSource.AUTONOMY,
            tuning_filter=FilterTag.EXPERT_MODE),
        'preroll_priority':
        TunableEnumEntry(
            description=
            "\n            The priority of the context of the autonomy roll.\n            Use this if you want the preroll's priority to either be higher or\n            lower because the sim could be running another interaction at\n            the time preroll autonomy ping is run.\n            ",
            tunable_type=Priority,
            default=Priority.Low,
            tuning_filter=FilterTag.EXPERT_MODE),
        'allow_unreachable_destinations':
        Tunable(
            description=
            "\n            If checked, autonomy will allow interactions that WILL fail when\n            run because the objects are unreachable. If not checked, autonomy\n            won't even return interactions that have unreachable destinations.\n            Interactions with unreachable destinations will just score really\n            high instead of be tested out.\n            \n            This is checked to true for things like the mailman where we want\n            him to do a failure transition when delivering mail to an unreachable\n            mailbox so it's visible to the player that the mailbox is unroutable.\n            ",
            tunable_type=bool,
            default=False)
    }

    def run_preroll(self, sim):
        autonomy_service = services.autonomy_service()
        context = InteractionContext(sim,
                                     self.preroll_source,
                                     self.preroll_priority,
                                     client=None,
                                     pick=None)
        autonomy_distance_estimation_behavior = AutonomyDistanceEstimationBehavior.ALLOW_UNREACHABLE_LOCATIONS if self.allow_unreachable_destinations else AutonomyDistanceEstimationBehavior.FULL
        autonomy_request = AutonomyRequest(
            sim,
            autonomy_mode=FullAutonomy,
            skipped_static_commodities=AutonomyComponent.
            STANDARD_STATIC_COMMODITY_SKIP_SET,
            limited_autonomy_allowed=False,
            context=context,
            distance_estimation_behavior=autonomy_distance_estimation_behavior,
            autonomy_mode_label_override='NPCPrerollAutonomy')
        scored_interactions = autonomy_service.score_all_interactions(
            autonomy_request)
        compatible_scored_interactions = tuple([
            scored_interaction_data
            for scored_interaction_data in scored_interactions
            if self.super_affordance_compatibility(
                scored_interaction_data.interaction.affordance)
        ])
        chosen_interaction = autonomy_service.choose_best_interaction(
            compatible_scored_interactions, autonomy_request)
        autonomy_request.invalidate_created_interactions(
            excluded_si=chosen_interaction)
        return chosen_interaction
Esempio n. 3
0
class AffordanceFilterFactory(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'affordance_filter':
        TunableAffordanceFilterSnippet(
            description=
            '\n            Affordances this modifier affects.\n            ')
    }

    def __call__(self, affordance):
        return self.affordance_filter(affordance)
Esempio n. 4
0
class ObjectPart(metaclass=TunedInstanceMetaclass,
                 manager=services.object_part_manager()):
    __qualname__ = 'ObjectPart'
    INSTANCE_TUNABLES = {
        'supported_posture_types':
        TunablePostureTypeListSnippet(
            description=
            'The postures supported by this part. If empty, assumes all postures are supported.'
        ),
        'supported_affordance':
        OptionalTunable(
            TunableAffordanceFilterSnippet(
                description='Affordances supported by the part')),
        'bone_names':
        TunableList(
            Tunable(str, '_ctnm_XXX_'),
            description='The list of bone names that make up this part.'),
        'subroot':
        TunableReference(
            services.subroot_manager(),
            description='The reference of the subroot definition in the part.')
    }
    _bone_names_for_part_suffices = None
    _bone_name_hashes_for_part_suffices = None

    @classmethod
    def get_bone_names_for_part_suffix(cls, part_suffix):
        if cls._bone_names_for_part_suffices is None:
            cls._bone_names_for_part_suffices = {}
        if part_suffix in cls._bone_names_for_part_suffices:
            return cls._bone_names_for_part_suffices[part_suffix]
        subroot_bone_names = []
        if cls.subroot is not None:
            subroot_bone_names = cls.subroot.bone_names
        else:
            subroot_bone_names = cls.bone_names
        if part_suffix:
            bone_names = frozenset(bone_name + part_suffix
                                   for bone_name in subroot_bone_names)
        else:
            bone_names = frozenset(subroot_bone_names)
        cls._bone_names_for_part_suffices[part_suffix] = bone_names
        return bone_names

    @classmethod
    def get_bone_name_hashes_for_part_suffix(cls, part_suffix):
        if cls._bone_name_hashes_for_part_suffices is None:
            cls._bone_name_hashes_for_part_suffices = {}
        if part_suffix in cls._bone_name_hashes_for_part_suffices:
            return cls._bone_name_hashes_for_part_suffices[part_suffix]
        bone_names = cls.get_bone_names_for_part_suffix(part_suffix)
        bone_name_hashes = frozenset(
            hash32(bone_name) for bone_name in bone_names)
        cls._bone_name_hashes_for_part_suffices[part_suffix] = bone_name_hashes
        return bone_name_hashes
class InteractionCancelCompatibility:
    INTERACTION_CANCEL_COMPATIBILITY = TunableMapping(
        description=
        '\n        A mapping between cancel reasons and affordance filters.  When a reason\n        is requested it runs the interaction though the affordance filter that\n        is requested along with all affordance filters in the hierarchy above\n        it.\n        \n        For example, the wedding will ensure the the interaction matches the\n        wedding, fire, and death reasons.\n        \n        The hierarchy of reasons is defined within python.  GPE support will be\n        needed to change or add new values to the hierarchy of reasons.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            An interaction canceling reason.\n            ',
            tunable_type=InteractionCancelReason,
            default=InteractionCancelReason.DEATH),
        value_type=TunableAffordanceFilterSnippet(
            description=
            '\n            An affordance filter that defines which interactions are able to\n            be canceled.  If the interaction is not compatible with the\n            affordance filter then it will be canceled.\n            '
        ))

    @classmethod
    def can_cancel_interaction_for_reason(cls, interaction, reason):
        while reason is not None:
            interaction_compatibility_filter = cls.INTERACTION_CANCEL_COMPATIBILITY.get(
                reason)
            if interaction_compatibility_filter is None:
                logger.warn(
                    'InteractionCancelReason {} not found within the INTERACTION_CANCEL_HIARCHY tuning skipping to next reason.',
                    reason)
            elif interaction_compatibility_filter(interaction):
                return False
            reason = InteractionCancelReason.get_next_reason(reason)
        return True

    @classmethod
    def cancel_interactions_for_reason(cls,
                                       sim,
                                       reason,
                                       finishing_type,
                                       cancel_reason_msg,
                                       additional_cancel_sources=None):
        sim_interactions = sim.get_all_running_and_queued_interactions()
        for interaction in sim_interactions:
            if cls.check_if_source_should_be_canceled(
                    interaction.context, additional_cancel_sources):
                if cls.can_cancel_interaction_for_reason(
                        interaction.affordance, reason):
                    interaction.cancel(finishing_type,
                                       cancel_reason_msg=cancel_reason_msg)

    @classmethod
    def check_if_source_should_be_canceled(cls,
                                           context,
                                           additional_cancel_sources=None):
        if additional_cancel_sources is not None and context.source in additional_cancel_sources:
            return True
        elif context.source is not InteractionSource.PIE_MENU and context.source is not InteractionSource.AUTONOMY and context.source is not InteractionSource.SCRIPT_WITH_USER_INTENT:
            return False
        return True
class CarryableComponent(
        Component,
        HasTunableFactory,
        AutoFactoryInit,
        component_name=objects.components.types.CARRYABLE_COMPONENT):
    class _CarryableAllowedHands(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'biped_allowed_hands':
            TunableVariant(locked_args={
                'both': (Hand.LEFT, Hand.RIGHT),
                'left_only': (Hand.LEFT, ),
                'right_only': (Hand.RIGHT, )
            },
                           default='both'),
            'quadruped_allowed_hands':
            TunableVariant(locked_args={
                'both': (Hand.LEFT, Hand.RIGHT),
                'mouth_only': (Hand.RIGHT, ),
                'back_only': (Hand.LEFT, )
            },
                           default='mouth_only')
        }

        def get_allowed_hands(self, sim):
            if sim is None:
                return self.biped_allowed_hands
            return sim.get_allowed_hands_type(self)

    class _CarryableTransitionConstraint(HasTunableSingletonFactory,
                                         AutoFactoryInit):
        FACTORY_TUNABLES = {
            'constraint_mobile':
            TunableList(
                description=
                '\n                The constraint to use when the Sim is in a mobile posture.\n                ',
                tunable=TunableGeometricConstraintVariant(
                    disabled_constraints={
                        'spawn_points', 'relative_circle', 'current_position'
                    })),
            'constraint_non_mobile':
            TunableList(
                description=
                '\n                The constraint to use when the Sim is not in a mobile posture.\n                ',
                tunable=TunableGeometricConstraintVariant(
                    disabled_constraints={
                        'spawn_points', 'relative_circle', 'current_position'
                    }))
        }

    DEFAULT_GEOMETRIC_TRANSITION_CONSTRAINT = _CarryableTransitionConstraint.TunableFactory(
        description=
        '\n        Unless specifically overridden, the constraint to use when transitioning\n        into and out of a carry for any carryable object.\n        '
    )
    DEFAULT_GEOMETRIC_TRANSITION_LARGE = TunableRange(
        description=
        '\n        This is a large transition distance. This is used by:\n            * TYAE humans picking up any pet\n        ',
        tunable_type=float,
        default=0.7,
        minimum=0)
    DEFAULT_GEOMETRIC_TRANSITION_MEDIUM = TunableRange(
        description=
        '\n        This is a medium transition distance. This is used by:\n            * TYAE humans picking up P humans\n        ',
        tunable_type=float,
        default=0.6,
        minimum=0)
    DEFAULT_GEOMETRIC_TRANSITION_SMALL = TunableRange(
        description=
        '\n        This is a small transition distance. This is used by:\n            * C humans picking up AE cats and AE small dogs\n        ',
        tunable_type=float,
        default=0.503,
        minimum=0)
    DEFAULT_GEOMETRIC_TRANSITION_TINY = TunableRange(
        description=
        '\n        This is a tiny transition distance. This is used by:\n            * C humans picking up C cats and C dogs and small dogs\n        ',
        tunable_type=float,
        default=0.419,
        minimum=0)
    DEFAULT_CARRY_AFFORDANCES = TunableList(
        description=
        '\n        The list of default carry affordances.\n        ',
        tunable=TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION)))
    PUT_IN_INVENTORY_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance used by carryable component to put objects in inventory.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    PUT_DOWN_HERE_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance used by carryable component to put down here via the\n        PutDownLiability liability.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    PUT_DOWN_ANYWHERE_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance used by carryable component to put down objects anywhere\n        via the PutDownLiability liability.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    FACTORY_TUNABLES = {
        'put_down_tuning':
        TunableVariant(
            description=
            '\n            Define how Sims prefer to put this object down.\n            ',
            reference=TunablePutDownStrategySpeciesMapping(
                description=
                '\n                Define specific costs for all the possible locations this object\n                could be put down. Also, define, if necessary, custom\n                interactions to put the object down.\n                \n                If this is not specified, and the default is used, the most\n                appropriate put down strategy is used. This allows the object to\n                be put down in most places (floor, inventory, slots). Also,\n                species might specify their own custom behavior.\n                '
            ),
            locked_args={'use_default': None},
            default='use_default'),
        'state_based_put_down_tuning':
        TunableMapping(
            description=
            '\n            A mapping from a state value to a putdownstrategy. If the owning\n            object is in any of the states tuned here, it will use that state\'s\n            associated putdownstrategy in place of the one putdownstrategy tuned\n            in the "put_down_tuning" field. If the object is in multiple states\n            listed in this mapping, the behavior is undefined.\n            ',
            key_type=TunableReference(
                description=
                '\n                The state value this object must be in in order to use the\n                associated putdownstrategy.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECT_STATE)),
            value_type=
            TunableVariant(reference=TunablePutDownStrategySpeciesMapping(
                description=
                '\n                    Tuning for how to score where a Sim might want to set an\n                    object down.\n                    '
            )),
            key_name='State',
            value_name='PutDownStrategy'),
        'carry_affordances':
        OptionalTunable(tunable=TunableList(tunable=TunableReference(
            description=
            '\n                    The versions of the HoldObject affordance that this object\n                    supports.\n                    ',
            manager=services.affordance_manager())),
                        disabled_name='use_default_affordances',
                        enabled_name='use_custom_affordances'),
        'provided_affordances':
        TunableProvidedAffordances(
            description=
            '\n            Affordances that are generated when a Sim holding this object\n            selects another object to interact with. The generated interactions\n            target the selected object, and have their carry target set to the\n            component\'s owner.\n            \n            By default, this is applied to Sims. e.g.: The "Drink" interaction\n            provides "Make Toast" on other Sims.\n            \n            Optionally, you can specify a tag to have the interaction appear on\n            other objects. e.g.: The "Hold Puppy" interaction might unlock "Put\n            Down Here" on sofas.\n                \n            Target defaults to the object selected (Invalid). Carry Target is\n            locked to this object being carried. If is_linked is\n            checked, the affordance will be linked to the interaction\n            carrying this object.\n            ',
            class_restrictions=('SuperInteraction', ),
            locked_args={
                'allow_self': False,
                'target': ParticipantType.Object,
                'carry_target': ParticipantType.CarriedObject
            }),
        'constraint_pick_up':
        OptionalTunable(
            description=
            '\n            A list of constraints that must be fulfilled in order to\n            interact with this object.\n            ',
            tunable=TunableList(tunable=TunableConstraintVariant(
                description=
                '\n                    A constraint that must be fulfilled in order to\n                    interact with this object.\n                    '
            ))),
        'allowed_hands_data':
        _CarryableAllowedHands.TunableFactory(),
        'holster_while_routing':
        Tunable(
            description=
            '\n            If True, the Sim will holster the object before routing and\n            unholster when the route is complete.\n            ',
            tunable_type=bool,
            default=False),
        'holster_compatibility':
        TunableAffordanceFilterSnippet(
            description=
            '\n            Define interactions for which holstering this object is\n            explicitly disallowed.\n            \n            e.g. The Scythe is tuned to be holster-incompatible with\n            sitting, meaning that Sims will holster the Sctyhe when sitting.\n            '
        ),
        'unholster_when_routing':
        TunableUnholsterWhileRoutingBehaviorVariant(),
        'prefer_owning_sim_inventory_when_not_on_home_lot':
        Tunable(
            description=
            "\n            If checked, this object will highly prefer to be put into the\n            owning Sim's inventory when being put down by the owning Sim on\n            a lot other than their home lot.\n            \n            Certain objects, like consumables, should be exempt from this.\n            ",
            tunable_type=bool,
            default=True),
        'is_valid_posture_graph_object':
        Tunable(
            description=
            '\n            If this is checked, this object is allowed to provide postures and\n            expand in the posture graph, despite its ability to be carried.\n            \n            Normally, for the performance reasons, carryable objects are not\n            posture providing objects. The preferred way of interacting with a\n            carryable object is marking it as a carry requirement in the ASM.\n            \n            However, there are objects for which this is not possible. For\n            example, Nesting Blocks are carryable, but toddlers interact with\n            them while in the "Sit On Ground" posture, which must be provided by\n            the object\'s parts.\n            \n            This field should be used with caution, since posture graph\n            generation is one of our slowest systems. Do not enable it on\n            objects such as food or drinks, since the world is bound to have\n            many of them.\n            ',
            tunable_type=bool,
            default=False),
        'portal_key_mask_flags':
        TunableEnumFlags(
            description=
            "\n            Any flag tuned here will be kept on the Sim routing context who's\n            picking up this object.  This will allow a Sim to pickup some\n            type of objects and still be allowed to transition through\n            some portals while carrying an object.\n            ",
            enum_type=PortalFlags,
            allow_no_flags=True),
        'reslot_plumbbob':
        OptionalTunable(
            description=
            '\n            If tuned, the plumbbob will be repositioned when this item is carried.\n            Reslot will always go away when sim stops carrying the object.\n            ',
            tunable=TunableReslotPlumbbob()),
        'defer_putdown':
        Tunable(
            description=
            '\n            If true, the put down will be deferred to the end of the route. \n            If false, put down will be done at the start of the route. This\n            should be the default behavior. \n            ',
            tunable_type=bool,
            default=False),
        'put_down_height_tolerance':
        TunableRange(
            description=
            '\n            Maximum height tolerance on the terrain we will use for the \n            placement of this object when asking FGL to find a spot on the\n            floor.\n            Having a high value here will make it so an object can be placed\n            in a terrain spot with a high slope that might result with\n            clipping depending on the width of the object.  The way this will\n            work will be using the object footprint, if the edges are at a\n            height higher than the height tolerance, then the location will\n            not be valid.\n            ',
            tunable_type=float,
            default=1.1,
            minimum=0)
    }

    def __init__(self, owner, **kwargs):
        super().__init__(owner, **kwargs)
        self._attempted_putdown = False
        self._attempted_alternative_putdown = False
        self._cached_put_down_strategy = None

    @property
    def attempted_putdown(self):
        return self._attempted_putdown

    @property
    def attempted_alternative_putdown(self):
        return self._attempted_alternative_putdown

    @property
    def ideal_slot_type_set(self):
        put_down_strategy = self.get_put_down_strategy()
        return put_down_strategy.ideal_slot_type_set

    @componentmethod
    def get_carry_object_posture(self):
        if self.carry_affordances is None:
            return CarryPostureStaticTuning.POSTURE_CARRY_OBJECT
        return self.carry_affordances[0].provided_posture_type

    @componentmethod_with_fallback(lambda *_, **__: ())
    def get_allowed_hands(self, sim):
        return self.allowed_hands_data.get_allowed_hands(sim)

    @componentmethod_with_fallback(lambda *_, **__: 0)
    def get_portal_key_make_for_carry(self):
        return self.portal_key_mask_flags

    @componentmethod
    def should_unholster(self, *args, **kwargs):
        return self.unholster_when_routing.should_unholster(*args, **kwargs)

    @componentmethod
    def get_put_down_strategy(self, parent=DEFAULT):
        if self._cached_put_down_strategy is None:
            parent = self.owner.parent if parent is DEFAULT else parent
            species = parent.species if parent is not None else Species.HUMAN
            for (state_value, put_down_strategy
                 ) in self.state_based_put_down_tuning.items():
                if self.owner.state_value_active(state_value):
                    self._cached_put_down_strategy = put_down_strategy.get(
                        species)
                    break
            else:
                if self.put_down_tuning is not None:
                    self._cached_put_down_strategy = self.put_down_tuning.get(
                        species)
            if self._cached_put_down_strategy is None:
                put_down_strategy = parent.get_default_put_down_strategy()
                self._cached_put_down_strategy = put_down_strategy
        return self._cached_put_down_strategy

    def _get_carry_transition_distance_for_sim(self, sim):
        carry_sim = self.owner.sim_info
        carrying_sim = sim.sim_info
        if carry_sim.is_toddler:
            return self.DEFAULT_GEOMETRIC_TRANSITION_MEDIUM
        if carrying_sim.is_teen_or_older:
            return self.DEFAULT_GEOMETRIC_TRANSITION_LARGE
        if carry_sim.is_teen_or_older:
            return self.DEFAULT_GEOMETRIC_TRANSITION_SMALL
        return self.DEFAULT_GEOMETRIC_TRANSITION_TINY

    def _get_adjusted_circle_constraint(self, sim, constraint):
        if not self.owner.is_sim:
            return constraint
        if not isinstance(constraint, TunedCircle):
            return constraint
        ideal_radius_override = self._get_carry_transition_distance_for_sim(
            sim)
        constraint = copy.copy(constraint)
        constraint.ideal_radius_width = constraint.ideal_radius_width / constraint.ideal_radius * ideal_radius_override
        constraint.radius = constraint.radius / constraint.ideal_radius * ideal_radius_override
        constraint.ideal_radius = ideal_radius_override
        return constraint

    @componentmethod
    def get_carry_transition_constraint(self,
                                        sim,
                                        position,
                                        routing_surface,
                                        cost=0,
                                        mobile=True):
        constraints = self.DEFAULT_GEOMETRIC_TRANSITION_CONSTRAINT
        constraints = constraints.constraint_mobile if mobile else constraints.constraint_non_mobile
        final_constraint = Anywhere()
        for constraint in constraints:
            if mobile:
                constraint = self._get_adjusted_circle_constraint(
                    sim, constraint)
            final_constraint = final_constraint.intersect(
                constraint.create_constraint(None,
                                             None,
                                             target_position=position,
                                             routing_surface=routing_surface))
        final_constraint = final_constraint.generate_constraint_with_cost(cost)
        final_constraint = final_constraint._copy(_multi_surface=True)
        return final_constraint

    @componentmethod
    def get_pick_up_constraint(self, sim):
        if self.constraint_pick_up is None:
            return
        final_constraint = Anywhere()
        for constraint in self.constraint_pick_up:
            constraint = self._get_adjusted_circle_constraint(sim, constraint)
            constraint = constraint.create_constraint(sim, target=self.owner)
            final_constraint = final_constraint.intersect(constraint)
        final_constraint = final_constraint._copy(_multi_surface=True)
        return final_constraint

    @componentmethod
    def get_provided_aops_gen(self, target, context, **kwargs):
        for provided_affordance_data in self.provided_affordances:
            if not provided_affordance_data.affordance.is_affordance_available(
                    context=context):
                continue
            if not provided_affordance_data.object_filter.is_object_valid(
                    target):
                continue
            if provided_affordance_data.affordance.is_social and not target.is_sim:
                affordance = provided_affordance_data.affordance
                interaction_target = self.owner
                preferred_objects = (target, )
            else:
                affordance = CarryTargetInteraction.generate(
                    provided_affordance_data.affordance, self.owner)
                interaction_target = target
                preferred_objects = ()
            depended_on_si = None
            parent = self.owner.parent
            if parent is not None:
                if parent.is_sim:
                    carry_posture = parent.posture_state.get_carry_posture(
                        self.owner)
                    if carry_posture is not None:
                        if provided_affordance_data.is_linked:
                            depended_on_si = carry_posture.source_interaction
            yield from affordance.potential_interactions(
                interaction_target,
                context,
                depended_on_si=depended_on_si,
                preferred_objects=preferred_objects,
                **kwargs)

    def component_super_affordances_gen(self, **kwargs):
        if self.carry_affordances is None:
            affordances = self.DEFAULT_CARRY_AFFORDANCES
        else:
            affordances = self.carry_affordances
        for affordance in affordances:
            yield affordance

    def component_interactable_gen(self):
        yield self

    def on_state_changed(self, state, old_value, new_value, from_init):
        if new_value in self.state_based_put_down_tuning or old_value in self.state_based_put_down_tuning:
            self._cached_put_down_strategy = None

    def component_reset(self, reset_reason):
        self.reset_put_down_count()

    @componentmethod
    def get_initial_put_down_position(self, carrying_sim=None):
        carrying_sim = carrying_sim or self.owner.parent
        if carrying_sim is None:
            return (self.owner.position, self.owner.routing_surface)
        additional_put_down_distance = carrying_sim.posture.additional_put_down_distance
        position = carrying_sim.position + carrying_sim.forward * (
            carrying_sim.object_radius + additional_put_down_distance)
        sim_los_constraint = carrying_sim.lineofsight_component.constraint
        if not sims4.geometry.test_point_in_compound_polygon(
                position, sim_los_constraint.geometry.polygon):
            position = carrying_sim.position
        return (position, carrying_sim.routing_surface)

    @componentmethod
    def get_put_down_aop(self,
                         interaction,
                         context,
                         alternative_multiplier=1,
                         own_inventory_multiplier=1,
                         object_inventory_multiplier=DEFAULT,
                         in_slot_multiplier=DEFAULT,
                         on_floor_multiplier=1,
                         visibility_override=None,
                         display_name_override=None,
                         additional_post_run_autonomy_commodities=None,
                         add_putdown_liability=False,
                         **kwargs):
        sim = interaction.sim
        owner = self.owner
        if owner.transient:
            return self._get_destroy_aop(sim, **kwargs)
        put_down_strategy = self.get_put_down_strategy(parent=sim)
        if object_inventory_multiplier is DEFAULT:
            object_inventory_multiplier = sim.get_put_down_object_inventory_cost_override(
            )
        if in_slot_multiplier is DEFAULT:
            in_slot_multiplier = sim.get_put_down_slot_cost_override()
        slot_types_and_costs = self.get_slot_types_and_costs(
            multiplier=in_slot_multiplier)
        (terrain_transform,
         terrain_routing_surface) = self._get_terrain_transform(interaction)
        objects = self._get_objects_with_inventory(interaction)
        objects = [obj for obj in objects if obj.can_access_for_putdown(sim)]
        if put_down_strategy.floor_cost is not None and on_floor_multiplier is not None:
            world_cost = put_down_strategy.floor_cost * on_floor_multiplier
        else:
            world_cost = None
        if put_down_strategy.inventory_cost is not None and own_inventory_multiplier is not None:
            sim_inventory_cost = put_down_strategy.inventory_cost * own_inventory_multiplier
        else:
            sim_inventory_cost = None
        if put_down_strategy.object_inventory_cost is not None and object_inventory_multiplier is not None:
            object_inventory_cost = put_down_strategy.object_inventory_cost * object_inventory_multiplier
        else:
            object_inventory_cost = None
        if not put_down_strategy.affordances:
            self._attempted_alternative_putdown = True
        if not self._attempted_alternative_putdown or self.owner.is_sim:
            self._attempted_alternative_putdown = True
            scored_aops = []
            for scored_aop in self._gen_affordance_score_and_aops(
                    interaction,
                    slot_types_and_costs=slot_types_and_costs,
                    world_cost=world_cost,
                    sim_inventory_cost=sim_inventory_cost,
                    object_inventory_cost=object_inventory_cost,
                    terrain_transform=terrain_transform,
                    terrain_routing_surface=terrain_routing_surface,
                    objects_with_inventory=objects,
                    visibility_override=visibility_override,
                    display_name_override=display_name_override,
                    additional_post_run_autonomy_commodities=
                    additional_post_run_autonomy_commodities,
                    multiplier=alternative_multiplier,
                    add_putdown_liability=add_putdown_liability):
                if scored_aop.aop.test(context):
                    scored_aops.append(scored_aop)
            if scored_aops:
                scored_aops.sort(key=operator.itemgetter(0))
                return scored_aops[-1].aop
        affordance = CarryableComponent.PUT_DOWN_ANYWHERE_AFFORDANCE
        if add_putdown_liability:
            liabilities = ((PutDownLiability.LIABILITY_TOKEN,
                            PutDownLiability(self.owner)), )
        else:
            liabilities = ()
        aop = AffordanceObjectPair(
            affordance,
            self.owner,
            affordance,
            None,
            slot_types_and_costs=slot_types_and_costs,
            world_cost=world_cost,
            sim_inventory_cost=sim_inventory_cost,
            object_inventory_cost=object_inventory_cost,
            terrain_transform=terrain_transform,
            terrain_routing_surface=terrain_routing_surface,
            objects_with_inventory=objects,
            visibility_override=visibility_override,
            display_name_override=display_name_override,
            additional_post_run_autonomy_commodities=
            additional_post_run_autonomy_commodities,
            liabilities=liabilities,
            **kwargs)
        self._attempted_putdown = True
        return aop

    def _gen_affordance_score_and_aops(
            self,
            interaction,
            slot_types_and_costs,
            world_cost,
            sim_inventory_cost,
            object_inventory_cost,
            terrain_transform,
            terrain_routing_surface,
            objects_with_inventory,
            visibility_override,
            display_name_override,
            additional_post_run_autonomy_commodities,
            multiplier=1,
            add_putdown_liability=False):
        put_down_strategy = self.get_put_down_strategy()
        for affordance in put_down_strategy.affordances:
            if add_putdown_liability:
                liabilities = ((PutDownLiability.LIABILITY_TOKEN,
                                PutDownLiability(self.owner)), )
            else:
                liabilities = ()
            aop = AffordanceObjectPair(
                affordance,
                self.owner,
                affordance,
                None,
                slot_types_and_costs=slot_types_and_costs,
                world_cost=world_cost,
                sim_inventory_cost=sim_inventory_cost,
                object_inventory_cost=object_inventory_cost,
                terrain_transform=terrain_transform,
                terrain_routing_surface=terrain_routing_surface,
                objects_with_inventory=objects,
                visibility_override=visibility_override,
                display_name_override=display_name_override,
                additional_post_run_autonomy_commodities=
                additional_post_run_autonomy_commodities,
                liabilities=liabilities)
            yield ScoredAOP(multiplier, aop)

    def _get_cost_for_slot_type(self, slot_type):
        put_down_strategy = self.get_put_down_strategy()
        if slot_type in self.owner.ideal_slot_types:
            return put_down_strategy.preferred_slot_cost
        return put_down_strategy.normal_slot_cost

    def get_slot_types_and_costs(self, multiplier=1):
        slot_types_and_costs = []
        for slot_type in self.owner.all_valid_slot_types:
            cost = self._get_cost_for_slot_type(slot_type)
            if cost is not None and multiplier is not None:
                cost *= multiplier
            else:
                cost = None
            slot_types_and_costs.append((slot_type, cost))
        return slot_types_and_costs

    def _get_terrain_transform(self, interaction):
        if not self.owner.is_sim and self.owner.footprint_component is None:
            return (None, None)
        else:
            sim = interaction.sim
            put_down_position = interaction.interaction_parameters.get(
                'put_down_position')
            put_down_routing_surface = interaction.interaction_parameters.get(
                'put_down_routing_surface')
            if put_down_position is None:
                (starting_position, starting_routing_surface
                 ) = self.get_initial_put_down_position(carrying_sim=sim)
            else:
                starting_position = put_down_position
                starting_routing_surface = put_down_routing_surface
            starting_location = placement.create_starting_location(
                position=starting_position,
                orientation=sim.orientation,
                routing_surface=starting_routing_surface)
            if self.owner.is_sim:
                search_flags = FGLSearchFlagsDefaultForSim | FGLSearchFlag.STAY_IN_CURRENT_BLOCK
                fgl_context_fn = functools.partial(
                    placement.create_fgl_context_for_sim,
                    search_flags=search_flags)
            else:
                search_flags = FGLSearchFlag.STAY_IN_CURRENT_BLOCK | FGLSearchFlag.SHOULD_TEST_ROUTING | FGLSearchFlag.CALCULATE_RESULT_TERRAIN_HEIGHTS | FGLSearchFlag.DONE_ON_MAX_RESULTS | FGLSearchFlag.SHOULD_TEST_BUILDBUY
                fgl_context_fn = functools.partial(
                    placement.create_fgl_context_for_object,
                    search_flags=search_flags)
            MAX_PUTDOWN_STEPS = 8
            MAX_PUTDOWN_DISTANCE = 10
            fgl_context = fgl_context_fn(
                starting_location,
                self.owner,
                max_steps=MAX_PUTDOWN_STEPS,
                max_distance=MAX_PUTDOWN_DISTANCE,
                height_tolerance=self.put_down_height_tolerance)
            (position, orientation) = placement.find_good_location(fgl_context)
            if position is not None:
                put_down_transform = sims4.math.Transform(
                    position, orientation)
                return (put_down_transform, starting_routing_surface)
        return (None, None)

    def _get_objects_with_inventory(self, interaction):
        objects = []
        inventory_item = self.owner.inventoryitem_component
        if inventory_item is not None:
            if CarryableComponent.PUT_IN_INVENTORY_AFFORDANCE is not None:
                for obj in inventory_item.valid_object_inventory_gen():
                    objects.append(obj)
        return objects

    def _get_destroy_aop(self, sim, **kwargs):
        affordance = CarryableComponent.PUT_DOWN_HERE_AFFORDANCE
        return AffordanceObjectPair(affordance,
                                    self.owner,
                                    affordance,
                                    None,
                                    put_down_transform=None,
                                    **kwargs)

    def reset_put_down_count(self):
        self._attempted_alternative_putdown = False
        self._attempted_putdown = False
        self._cached_put_down_strategy = None

    def on_object_carry(self, actor, *_, **__):
        if self.reslot_plumbbob is not None:
            reslot_plumbbob(actor, self.reslot_plumbbob)

    def on_object_uncarry(self, actor, *_, **__):
        if self.reslot_plumbbob is not None:
            unslot_plumbbob(actor)
Esempio n. 7
0
class AutonomyModifier:
    __qualname__ = 'AutonomyModifier'
    STATISTIC_RESTRICTIONS = (statistics.commodity.Commodity, statistics.statistic.Statistic, statistics.skill.Skill)
    FACTORY_TUNABLES = {'description': "\n            An encapsulation of a modification to Sim behavior.  These objects\n            are passed to the autonomy system to affect things like scoring,\n            which SI's are available, etc.\n            ", 'super_affordance_compatibility': TunableAffordanceFilterSnippet(description='\n            Tune this to provide suppression to certain affordances when an object has\n            this autonomy modifier.\n            EX: Tune this to exclude all on the buff for the maid to prevent\n                other sims from trying to chat with the maid while the maid is\n                doing her work.\n            To tune if this restriction is for autonomy only, etc, see\n            super_affordance_suppression_mode.\n            Note: This suppression will also apply to the owning sim! So if you\n                prevent people from autonomously interacting with the maid, you\n                also prevent the maid from doing self interactions. To disable\n                this, see suppress_self_affordances.\n            '), 'super_affordance_suppression_mode': TunableEnumEntry(description='\n            Setting this defines how to apply the settings tuned in Super Affordance Compatibility.', tunable_type=SuperAffordanceSuppression, default=SuperAffordanceSuppression.AUTONOMOUS_ONLY), 'super_affordance_suppress_on_add': Tunable(description='\n            If checked, then the suppression rules will be applied when the\n            modifier is added, potentially canceling interactions the owner is\n            running.\n            ', tunable_type=bool, default=False), 'suppress_self_affordances': Tunable(description="\n            If checked, the super affordance compatibility tuned for this \n            autonomy modifier will also apply to the sim performing self\n            interactions.\n            \n            If not checked, we will not do super_affordance_compatibility checks\n            if the target of the interaction is the same as the actor.\n            \n            Ex: Tune the maid's super_affordance_compatibility to exclude all\n                so that other sims will not chat with the maid. But disable\n                suppress_self_affordances so that the maid can still perform\n                interactions on herself (such as her No More Work interaction\n                that tells her she's finished cleaning).\n            ", tunable_type=bool, default=True), 'score_multipliers': TunableMapping(description='\n                Mapping of statistics to multipliers values to the autonomy\n                scores.  EX: giving motive_bladder a multiplier value of 2 will\n                make it so that that motive_bladder is scored twice as high as\n                it normally would be.\n                ', key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description='\n                    The stat the multiplier will apply to.\n                    '), value_type=Tunable(float, 1, description='\n                    The autonomy score multiplier for the stat.  Multiplies\n                    autonomy scores by the tuned value.\n                    ')), 'static_commodity_score_multipliers': TunableMapping(description='\n                Mapping of statistics to multipliers values to the autonomy\n                scores.  EX: giving motive_bladder a multiplier value of 2 will\n                make it so that that motive_bladder is scored twice as high as\n                it normally would be.\n                ', key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.STATIC_COMMODITY), description='\n                    The static commodity the multiplier will apply to.\n                    '), value_type=Tunable(float, 1, description='\n                    The autonomy score multiplier for the static commodity.  Multiplies\n                    autonomy scores by the tuned value.\n                    ')), 'relationship_score_multiplier_with_buff_on_target': TunableMapping(description="\n                Mapping of buffs to multipliers.  The buff must exist on the TARGET sim.\n                If it does, this value will be multiplied into the relationship score.\n                \n                Example: The make children desire to socialize with children, you can add \n                this autonomy modifier to the child's age buff.  You can then map it with \n                a key to the child buff to apply a positive multiplier.  An alternative \n                would be to create a mapping to every other age and apply a multiplier that \n                is smaller than 1.\n                ", key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.BUFF), description='\n                    The buff that the target sim must have to apply this multiplier.\n                    '), value_type=Tunable(float, 1, description='\n                    The multiplier to apply.\n                    ')), 'locked_stats': TunableList(TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description='\n                    The stat the modifier will apply to.\n                    '), description='\n                List of the stats we locked from this modifier.  Locked stats\n                are set to their maximum values and then no longer allowed to\n                decay.\n                '), 'decay_modifiers': CommodityDecayModifierMapping(description='\n                Statistic to float mapping for decay modifiers for\n                statistics.  All decay modifiers are multiplied together along\n                with the decay rate.\n                '), 'skill_tag_modifiers': TunableMapping(description='\n                The skill_tag to float mapping of skill modifiers.  Skills with\n                these tags will have their amount gained multiplied by the\n                sum of all the tuned values.\n                ', key_type=TunableEnumEntry(tag.Tag, tag.Tag.INVALID, description='\n                    What skill tag to apply the modifier on.\n                    '), value_type=Tunable(float, 0)), 'commodities_to_add': TunableList(TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=statistics.commodity.Commodity), description='\n                Commodites that are added while this autonomy modifier is\n                active.  These commodities are removed when the autonomy\n                modifier is removed.\n                '), 'only_scored_stats': OptionalTunable(TunableList(TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS), description='\n                    List of statistics that will only be considered when doing\n                    autonomy.\n                    '), tuning_filter=FilterTag.EXPERT_MODE, description="\n                If enabled, the sim in this role state will consider ONLY these\n                stats when doing autonomy. EX: for the maid, only score\n                commodity_maidrole_clean so she doesn't consider doing things\n                that she shouldn't care about.\n                "), 'only_scored_static_commodities': OptionalTunable(TunableList(StaticCommodity.TunableReference(), description='\n                    List of statistics that will only be considered when doing\n                    autonomy.\n                    '), tuning_filter=FilterTag.EXPERT_MODE, description='\n                If enabled, the sim in this role state will consider ONLY these\n                static commodities when doing autonomy. EX: for walkbys, only\n                consider the ringing the doorbell\n                '), 'stat_use_multiplier': TunableMapping(description='\n                List of stats and multiplier to affect their increase-decrease.\n                All stats on this list whenever they get modified (e. by a \n                constant modifier on an interaction, an interaction result...)\n                will apply the multiplier to their modified values. \n                e. A toilet can get a multiplier to decrease the repair rate\n                when its used, for this we would tune the commodity\n                brokenness and the multiplier 0.5 (to decrease its effect)\n                This tunable multiplier will affect the object statistics\n                not the ones for the sims interacting with it.\n                ', key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description='\n                    The stat the multiplier will apply to.\n                    '), value_type=TunableTuple(description='\n                    Float value to apply to the statistic whenever its\n                    affected.  Greater than 1.0 if you want to increase.\n                    Less than 1.0 if you want a decrease (>0.0). \n                    A value of 0 is considered invalid and is skipped.\n                    ', multiplier=Tunable(description='\n                        Float value to apply to the statistic whenever its\n                        affected.  Greater than 1.0 if you want to increase.\n                        Less than 1.0 if you want a decrease (>0.0). \n                        A value of 0 is considered invalid and is skipped.\n                        ', tunable_type=float, default=1.0), apply_direction=TunableEnumEntry(StatisticChangeDirection, StatisticChangeDirection.BOTH, description='\n                        Direction on when the multiplier should work on the \n                        statistic.  For example a decrease on an object \n                        brokenness rate, should not increase the time it takes to \n                        repair it.\n                        '))), 'relationship_multipliers': TunableMapping(description='\n                List of relationship tracks and multiplier to affect their\n                increase or decrease of track value. All stats on this list\n                whenever they get modified (e. by a constant modifier on an\n                interaction, an interaction result...) will apply the\n                multiplier to their modified values. e.g. A LTR_Friendship_Main\n                can get a multiplier to decrease the relationship decay when\n                interacting with someone with a given trait, for this we would\n                tune the relationship track LTR_Friendship_Main and the\n                multiplier 0.5 (to decrease its effect)\n                ', key_type=relationships.relationship_track.RelationshipTrack.TunableReference(description='\n                    The Relationship track the multiplier will apply to.\n                    '), value_type=TunableTuple(description="\n                    Float value to apply to the statistic whenever it's\n                    affected.  Greater than 1.0 if you want to increase.\n                    Less than 1.0 if you want a decrease (>0.0).\n                    ", multiplier=Tunable(tunable_type=float, default=1.0), apply_direction=TunableEnumEntry(description='\n                        Direction on when the multiplier should work on the \n                        statistic.  For example a decrease on an object \n                        brokenness rate, should not increase the time it takes to \n                        repair it.\n                        ', tunable_type=StatisticChangeDirection, default=StatisticChangeDirection.BOTH))), 'off_lot_autonomy_rule': OptionalTunable(TunableVariant(description="\n                The rules to apply for how autonomy handle on-lot and off-lot\n                targets.\n                \n                DEFAULT:\n                    Sims will behave according to their default behavior.  Off-\n                    lot sims who are outside the lot's tolerance will not\n                    autonomously perform interactions on the lot.  Sims will\n                    only autonomously perform off-lot interactions within their\n                    off-lot radius.\n                ON_LOT_ONLY:\n                    Sims will only consider targets on the active lot.  They\n                    will ignore the off lot radius and off lot tolerance\n                    settings.\n                OFF_LOT_ONLY:\n                    Sims will only consider targets that are off the active lot.\n                    They will ignore the off lot tolerance settings, but they\n                    will respect the off lot radius.\n                UNLIMITED:\n                    Sims will consider all objects regardless of on/off lot\n                    status.\n                ", default_behavior=TunableTuple(description="\n                    Sims will behave according to their default behavior.  Off-\n                    lot sims who are outside the lot's tolerance will not\n                    autonomously perform interactions on the lot.  Sims will\n                    only autonomously perform off-lot interactions within their\n                    off-lot radius.\n                    ", locked_args={'rule': OffLotAutonomyRules.DEFAULT}, tolerance=Tunable(description='\n                        This is how many meters the Sim can be off of the lot while still being \n                        considered on the lot for the purposes of autonomy.  For example, if \n                        this is set to 5, the sim can be 5 meters from the edge of the lot and \n                        still consider all the objects on the lot for autonomy.  If the sim were \n                        to step 6 meters from the lot, the sim would be considered off the lot \n                        and would only score off-lot objects that are within the off lot radius.\n                        \n                        Note: If this value is set to anything below 0, it will use the global \n                        default in autonomy.autonomy_modes.OFF_LOT_TOLERANCE.\n                        ', tunable_type=float, default=-1), radius=Tunable(description='\n                        The radius around the sim in which he will consider off-lot objects.  If it is \n                        0, the Sim will not consider off-lot objects at all.  This is not recommended \n                        since it will keep them from running any interactions unless they are already \n                        within the tolerance for that lot (set with Off Lot Tolerance).\n                        \n                        Note: If this value is less than zero, the range is considered infinite.  The \n                        sim will consider every off-lot object.\n                        ', tunable_type=float, default=0)), on_lot_only=TunableTuple(description='\n                    Sims will only consider targets on the active lot.\n                    ', locked_args={'rule': OffLotAutonomyRules.ON_LOT_ONLY, 'tolerance': 0, 'radius': 0}), off_lot_only=TunableTuple(description='\n                    Sims will only consider targets that are off the active lot. \n                    ', locked_args={'rule': OffLotAutonomyRules.OFF_LOT_ONLY, 'tolerance': 0}, radius=Tunable(description='\n                        The radius around the sim in which he will consider off-lot objects.  If it is \n                        0, the Sim will not consider off-lot objects at all.  This is not recommended \n                        since it will keep them from running any interactions unless they are already \n                        within the tolerance for that lot (set with Off Lot Tolerance).\n                        \n                        Note: If this value is less than zero, the range is considered infinite.  The \n                        sim will consider every off-lot object.\n                        ', tunable_type=float, default=-1)), unlimited=TunableTuple(description='\n                    Sims will consider all objects regardless of on/off lot\n                    status.\n                    ', locked_args={'rule': OffLotAutonomyRules.UNLIMITED, 'tolerance': 0, 'radius': 0}), default='default_behavior')), 'override_convergence_value': OptionalTunable(description="\n            If enabled it will set a new convergence value to the tuned\n            statistics.  The decay of those statistics will start moving\n            toward the new convergence value.\n            Convergence value will apply as long as these modifier is active,\n            when modifier is removed, convergence value will return to default\n            tuned value.\n            As a tuning restriction when this modifier gets removed we will \n            reset the convergence to its original value.  This means that we \n            don't support two states at the same time overwriting convergence\n            so we should'nt tune multiple convergence overrides on the same \n            object.\n            ", tunable=TunableMapping(description='\n                Mapping of statistic to new convergence value.\n                ', key_type=Commodity.TunableReference(), value_type=Tunable(description='\n                    Value to which the statistic should convert to.\n                    ', tunable_type=int, default=0)), disabled_name='Use_default_convergence', enabled_name='Set_new_convergence_value'), 'subject': TunableVariant(description='\n            Specifies to whom this autonomy modifier will apply.\n            - Apply to owner: Will apply the modifiers to the object or sim who \n            is triggering the modifier.  \n            e.g Buff will apply the modifiers to the sim when he gets the buff.  \n            An object will apply the modifiers to itself when it hits a state.\n            - Apply to interaction participant:  Will save the modifiers to \n            be only triggered when the object/sim who holds the modifier \n            is on an interaction.  When the interaction starts the the subject\n            tuned will get the modifiers during the duration of the interaction. \n            e.g A sim with modifiers to apply on an object will only trigger \n            when the sim is interactin with an object.\n            ', apply_on_interaction_to_participant=OptionalTunable(TunableEnumFlags(description='\n                    Subject on which the modifiers should apply.  When this is set\n                    it will mean that the autonomy modifiers will trigger on a \n                    subect different than the object where they have been added.\n                    e.g. a shower ill have hygiene modifiers that have to affect \n                    the Sim ', enum_type=ParticipantType, default=ParticipantType.Object)), default='apply_to_owner', locked_args={'apply_to_owner': False})}

    def __init__(self, score_multipliers=None, static_commodity_score_multipliers=None, relationship_score_multiplier_with_buff_on_target=None, super_affordance_compatibility=None, super_affordance_suppression_mode=SuperAffordanceSuppression.AUTONOMOUS_ONLY, suppress_self_affordances=False, super_affordance_suppress_on_add=False, locked_stats=set(), decay_modifiers=None, statistic_modifiers=None, skill_tag_modifiers=None, commodities_to_add=(), only_scored_stats=None, only_scored_static_commodities=None, stat_use_multiplier=None, relationship_multipliers=None, off_lot_autonomy_rule=None, override_convergence_value=None, subject=None, exclusive_si=None):
        self._super_affordance_compatibility = super_affordance_compatibility
        self._super_affordance_suppression_mode = super_affordance_suppression_mode
        self._suppress_self_affordances = suppress_self_affordances
        self._super_affordance_suppress_on_add = super_affordance_suppress_on_add
        self._score_multipliers = score_multipliers
        self._locked_stats = set(locked_stats)
        self._decay_modifiers = decay_modifiers
        self._statistic_modifiers = statistic_modifiers
        self._relationship_score_multiplier_with_buff_on_target = relationship_score_multiplier_with_buff_on_target
        self._skill_tag_modifiers = skill_tag_modifiers
        self._commodities_to_add = commodities_to_add
        self._stat_use_multiplier = stat_use_multiplier
        self._relationship_multipliers = relationship_multipliers
        self._off_lot_autonomy_rule = off_lot_autonomy_rule
        self._subject = subject
        self._override_convergence_value = override_convergence_value
        self._exclusive_si = exclusive_si
        self._skill_tag_modifiers = {}
        if skill_tag_modifiers:
            for (skill_tag, skill_tag_modifier) in skill_tag_modifiers.items():
                skill_modifier = SkillTagMultiplier(skill_tag_modifier, StatisticChangeDirection.INCREASE)
                self._skill_tag_modifiers[skill_tag] = skill_modifier
        if static_commodity_score_multipliers:
            if self._score_multipliers is not None:
                self._score_multipliers = FrozenAttributeDict(self._score_multipliers, static_commodity_score_multipliers)
            else:
                self._score_multipliers = static_commodity_score_multipliers
        self._static_commodity_score_multipliers = static_commodity_score_multipliers
        self._only_scored_stat_types = None
        if only_scored_stats is not None:
            self._only_scored_stat_types = []
            self._only_scored_stat_types.extend(only_scored_stats)
        if only_scored_static_commodities is not None:
            if self._only_scored_stat_types is None:
                self._only_scored_stat_types = []
            self._only_scored_stat_types.extend(only_scored_static_commodities)

    def __repr__(self):
        return standard_auto_repr(self)

    @property
    def exclusive_si(self):
        return self._exclusive_si

    def affordance_suppressed(self, sim, aop_or_interaction, user_directed=DEFAULT):
        user_directed = aop_or_interaction.is_user_directed if user_directed is DEFAULT else user_directed
        if not self._suppress_self_affordances and aop_or_interaction.target == sim:
            return False
        affordance = aop_or_interaction.affordance
        if self._super_affordance_compatibility is None:
            return False
        if user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.AUTONOMOUS_ONLY:
            return False
        if not user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.USER_DIRECTED:
            return False
        return not self._super_affordance_compatibility(affordance)

    def locked_stats_gen(self):
        for stat in self._locked_stats:
            yield stat

    def get_score_multiplier(self, stat_type):
        if self._score_multipliers is not None and stat_type in self._score_multipliers:
            return self._score_multipliers[stat_type]
        return 1

    def get_stat_multiplier(self, stat_type, participant_type):
        if self._stat_use_multiplier is None:
            return 1
        if self._subject == participant_type and stat_type in self._stat_use_multiplier:
            return self._stat_use_multiplier[stat_type].multiplier
        return 1

    @property
    def subject(self):
        return self._subject

    @property
    def statistic_modifiers(self):
        return self._statistic_modifiers

    @property
    def statistic_multipliers(self):
        return self._stat_use_multiplier

    @property
    def relationship_score_multiplier_with_buff_on_target(self):
        return self._relationship_score_multiplier_with_buff_on_target

    @property
    def relationship_multipliers(self):
        return self._relationship_multipliers

    @property
    def decay_modifiers(self):
        return self._decay_modifiers

    @property
    def skill_tag_modifiers(self):
        return self._skill_tag_modifiers

    @property
    def commodities_to_add(self):
        return self._commodities_to_add

    @property
    def override_convergence(self):
        return self._override_convergence_value

    def is_locked(self, stat_type):
        if self._locked_stats and stat_type in self._locked_stats:
            return True
        return False

    def is_scored(self, stat_type):
        if self._only_scored_stat_types is None or stat_type in self._only_scored_stat_types:
            return True
        return False

    @property
    def off_lot_autonomy_rule(self):
        return self._off_lot_autonomy_rule

    @property
    def super_affordance_suppress_on_add(self):
        return self._super_affordance_suppress_on_add
Esempio n. 8
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)
Esempio n. 9
0
class GameObject(ClientObjectMixin, UseListMixin, ScriptObject):
    __qualname__ = 'GameObject'
    INSTANCE_TUNABLES = {'_focus_bone': Tunable(str, '_focus_', tuning_filter=FilterTag.EXPERT_MODE, description='The name of the bone that the focus system will used to look at.'), '_transient_tuning': Tunable(bool, False, tuning_filter=FilterTag.EXPERT_MODE, description='If transient the object will always be destroyed and never put down.', display_name='Transient'), 'additional_interaction_constraints': TunableList(TunableTuple(constraint=interactions.constraints.TunableConstraintVariant(description='A constraint that must be fulfilled in order to interact with this object.'), affordance_links=TunableAffordanceFilterSnippet()), tuning_filter=FilterTag.EXPERT_MODE, description='A list of constraints that must be fulfilled in order to run the linked affordances.'), 'autonomy_modifiers': TunableList(description='\n            List of autonomy modifiers that will be applied to the tuned\n            participant type.  These can be used to tune object variations.\n            ', tunable=TunableAutonomyModifier(locked_args={'commodities_to_add': (), 'score_multipliers': {}, 'super_affordance_compatibility': None, 'super_affordance_suppression_mode': autonomy.autonomy_modifier.SuperAffordanceSuppression.AUTONOMOUS_ONLY, 'suppress_self_affordances': False, 'only_scored_static_commodities': None, 'only_scored_stats': None, 'relationship_multipliers': None})), 'set_ico_as_carry_target': Tunable(description="\n            Whether or not the crafting process should set the carry target\n            to be the ICO.  Example Usage: Sheet Music has this set to false\n            because the sheet music is in the Sim's inventory and the Sim needs\n            to carry the guitar/violin.  This is a tunable on game object\n            because the ICO in the crafting process can be any game object.\n            ", tunable_type=bool, default=True), 'supported_posture_types': TunablePostureTypeListSnippet(description='\n            The postures supported by this part. If empty, assumes all postures \n            are supported.')}

    def __init__(self, definition, **kwargs):
        super().__init__(definition, **kwargs)
        self._on_location_changed_callbacks = None
        if self._part_data:
            self._parts = []
            for part_data in self._part_data:
                self._parts.append(objects.part.Part(self, part_data))
        else:
            self._in_use = None
        self._transient = None
        self._created_constraints = {}
        self._created_constraints_dirty = True
        self._household_owner_id = None
        self.new_in_inventory = True
        zone = services.current_zone()
        account_id = build_buy.get_user_in_build_buy(zone.id)
        if account_id is not None:
            self.set_household_owner_id(zone.lot.owner_household_id)
            self.set_post_bb_fixup_needed()
            zone.set_to_fixup_on_build_buy_exit(self)
        self._hidden_flags = 0
        self._local_tags = None
        self._is_surface = {}

    def has_tag(self, tag):
        if tag in self.definition.build_buy_tags:
            return True
        if self._local_tags and self._local_tags & set(tag):
            return True
        return False

    def get_tags(self):
        if self._local_tags:
            return self._local_tags | self.definition.build_buy_tags
        return self.definition.build_buy_tags

    def append_tags(self, tag_set):
        if self._local_tags:
            self._local_tags = self._local_tags | tag_set
        else:
            self._local_tags = tag_set

    def get_icon_info_data(self):
        return IconInfoData(obj_instance=self, obj_def_id=self.definition.id, obj_geo_hash=self.geometry_state, obj_material_hash=self.material_hash)

    @property
    def catalog_name(self):
        return get_object_catalog_name(self.definition.id)

    @property
    def catalog_description(self):
        return get_object_catalog_description(self.definition.id)

    @forward_to_components
    def populate_localization_token(self, token):
        self.definition.populate_localization_token(token)

    def is_hidden(self, allow_hidden_flags=0):
        if int(self._hidden_flags) & ~int(allow_hidden_flags):
            return True
        return False

    def has_hidden_flags(self, hidden_flags):
        if int(self._hidden_flags) & int(hidden_flags):
            return True
        return False

    def hide(self, hidden_reasons_to_add):
        self._hidden_flags = self._hidden_flags | hidden_reasons_to_add

    def show(self, hidden_reasons_to_remove):
        self._hidden_flags = self._hidden_flags & ~hidden_reasons_to_remove

    @property
    def transient(self):
        if self._transient is not None:
            return self._transient
        return self._transient_tuning

    @transient.setter
    def transient(self, value):
        self._transient = value

    def get_created_constraint(self, tuned_constraint):
        if self._created_constraints_dirty:
            self._created_constraints.clear()
            for tuned_additional_constraint in self.additional_interaction_constraints:
                constraint = tuned_additional_constraint['constraint']
                while constraint is not None:
                    self._created_constraints[constraint] = constraint.create_constraint(None, self)
            self._created_constraints_dirty = False
        return self._created_constraints.get(tuned_constraint)

    @forward_to_components
    def validate_definition(self):
        pass

    def _should_invalidate_location(self):
        parent = self.parent
        if parent is None:
            return True
        return parent._should_invalidate_location()

    def _notify_buildbuy_of_location_change(self, old_location):
        if self.persistence_group == PersistenceGroups.OBJECT and self._should_invalidate_location():
            invalidate_object_location(self.id, self.zone_id)

    def set_build_buy_lockout_state(self, lockout_state, lockout_timer=None):
        if self._build_buy_lockout_alarm_handler is not None:
            alarms.cancel_alarm(self._build_buy_lockout_alarm_handler)
            self._build_buy_lockout_alarm_handler = None
        elif self._build_buy_lockout and lockout_state:
            return
        if lockout_state and lockout_timer is not None:
            time_span_real_time = clock.interval_in_real_seconds(lockout_timer)
            self._build_buy_lockout_alarm_handler = alarms.add_alarm_real_time(self, time_span_real_time, lambda *_: self.set_build_buy_lockout_state(False))
        if lockout_state and not self.build_buy_lockout:
            self.reset(ResetReason.RESET_EXPECTED)
        self._build_buy_lockout = lockout_state
        self.resend_interactable()
        self.resend_tint()

    def on_location_changed(self, old_location):
        super().on_location_changed(old_location)
        self.clear_check_line_of_sight_cache()
        if self.id:
            self._update_persistence_group()
            self._notify_buildbuy_of_location_change(old_location)
            self.manager.on_location_changed(self)
            if self._on_location_changed_callbacks is not None:
                for callback in self._on_location_changed_callbacks:
                    callback(self, old_location, self.location)
            self._created_constraints_dirty = True

    def set_object_def_state_index(self, state_index):
        if type(self) != self.get_class_for_obj_state(state_index):
            logger.error("Attempting to change object {}'s state to one that would require a different runtime class.  This is not supported.", self, owner='tastle')
        self.apply_definition(self.definition, state_index)
        self.model = self._model
        self.rig = self._rig
        self.resend_state_index()

    def register_on_location_changed(self, callback):
        if self._on_location_changed_callbacks is None:
            self._on_location_changed_callbacks = [callback]
        else:
            self._on_location_changed_callbacks.append(callback)

    def unregister_on_location_changed(self, callback):
        self._on_location_changed_callbacks.remove(callback)
        if not self._on_location_changed_callbacks:
            self._on_location_changed_callbacks = None

    def is_on_active_lot(self, tolerance=0):
        return self.persistence_group == PersistenceGroups.OBJECT

    @property
    def is_in_navmesh(self):
        if self._routing_context is not None and self._routing_context.object_footprint_id is not None:
            return True
        return False

    def footprint_polygon_at_location(self, position, orientation):
        if self.footprint is None or self.routing_surface is None:
            return
        from placement import get_placement_footprint_polygon
        return get_placement_footprint_polygon(position, orientation, self.routing_surface, self.footprint)

    @property
    def object_radius(self):
        if self._routing_context is None:
            return routing.get_default_agent_radius()
        return self._routing_context.object_radius

    @object_radius.setter
    def object_radius(self, value):
        if self._routing_context is not None:
            self._routing_context.object_radius = value

    @distributor.fields.Field(op=distributor.ops.SetFocusScore)
    def focus_score(self):
        return FocusInterestTuning.FOCUS_INTEREST_LEVEL_TO_SCORE[self._focus_score]

    def facing_object(self, obj):
        raise RuntimeError('[bhill] This function is believed to be dead code and is scheduled for pruning. If this exception has been raised, the code is not dead and this exception should be removed.')
        forward = self.forward
        to_obj = sims4.math.vector_normalize(obj.position - self.position)
        cos_of_angle = sims4.math.vector_dot(forward, to_obj)
        if cos_of_angle < 0:
            cos_of_angle = 0
        return cos_of_angle

    @property
    def persistence_group(self):
        return self._persistence_group

    @persistence_group.setter
    def persistence_group(self, value):
        self._persistence_group = value

    def _update_persistence_group(self):
        if self.is_in_inventory():
            self.persistence_group = objects.persistence_groups.PersistenceGroups.OBJECT
            return
        if self.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT:
            if not services.current_zone().lot.is_position_on_lot(self.position, 0):
                remove_object_from_buildbuy_system(self.id, self.zone_id)
                self.persistence_group = objects.persistence_groups.PersistenceGroups.IN_OPEN_STREET
        elif self.persistence_group == objects.persistence_groups.PersistenceGroups.IN_OPEN_STREET and services.current_zone().lot.is_position_on_lot(self.position, 0):
            self.persistence_group = objects.persistence_groups.PersistenceGroups.OBJECT
            add_object_to_buildbuy_system(self.id, sims4.zone_utils.get_zone_id())

    def _add_to_world(self):
        if self.persistence_group == PersistenceGroups.OBJECT:
            add_object_to_buildbuy_system(self.id, sims4.zone_utils.get_zone_id())

    def _remove_from_world(self):
        if self.persistence_group == PersistenceGroups.OBJECT:
            remove_object_from_buildbuy_system(self.id, self.zone_id)

    def on_add(self):
        super().on_add()
        self._add_to_world()
        self.register_on_location_changed(self.inside_status_change)
        self.register_on_location_changed(self.natural_ground_status_change)
        if (self.flammable or self.fire_retardant) and not self.is_sim:
            fire_service = services.get_fire_service()
            self.register_on_location_changed(fire_service.flammable_object_location_changed)
        self.object_addition_complete()

    def on_remove(self):
        super().on_remove()
        self._remove_from_world()
        self.unregister_on_location_changed(self.inside_status_change)
        self.unregister_on_location_changed(self.natural_ground_status_change)
        if self.flammable and not self.is_sim:
            fire_service = services.get_fire_service()
            if fire_service:
                self.unregister_on_location_changed(fire_service.flammable_object_location_changed)

    def on_added_to_inventory(self):
        super().on_added_to_inventory()
        self._remove_from_world()
        self.visibility = VisibilityState(False)

    def on_removed_from_inventory(self):
        super().on_removed_from_inventory()
        self._add_to_world()
        self.visibility = VisibilityState(True)

    def on_buildbuy_exit(self):
        self.inside_status_change()
        self.natural_ground_status_change()

    def inside_status_change(self, *_, **__):
        if self.zone_id:
            if self.is_outside:
                self._set_placed_outside()
            else:
                self._set_placed_inside()

    def natural_ground_status_change(self, *_, **__):
        if self.zone_id:
            if self.is_on_natural_ground():
                self._set_placed_on_natural_ground()
            else:
                self._set_placed_off_natural_ground()

    @forward_to_components
    def _set_placed_outside(self):
        pass

    @forward_to_components
    def _set_placed_inside(self):
        pass

    @forward_to_components
    def _set_placed_on_natural_ground(self):
        pass

    @forward_to_components
    def _set_placed_off_natural_ground(self):
        pass

    @forward_to_components
    def object_addition_complete(self):
        pass

    @property
    def is_outside(self):
        routing_surface = self.routing_surface
        level = 0 if routing_surface is None else routing_surface.secondary_id
        try:
            return is_location_outside(self.zone_id, self.position, level)
        except RuntimeError:
            pass

    def is_on_natural_ground(self):
        if self.parent is not None:
            return False
        routing_surface = self.routing_surface
        level = 0 if routing_surface is None else routing_surface.secondary_id
        try:
            return is_location_natural_ground(self.zone_id, self.position, level)
        except RuntimeError:
            pass

    def on_child_added(self, child):
        super().on_child_added(child)
        self.get_raycast_root().on_leaf_child_changed()

    def on_child_removed(self, child):
        super().on_child_removed(child)
        self.get_raycast_root().on_leaf_child_changed()

    def on_leaf_child_changed(self):
        if self._raycast_context is not None:
            self._create_raycast_context()

    @property
    def focus_bone(self):
        if self._focus_bone is not None:
            return sims4.hash_util.hash32(self._focus_bone)
        return 0

    @property
    def forward_direction_for_picking(self):
        return sims4.math.Vector3.Z_AXIS()

    @property
    def route_target(self):
        parts = self.parts
        if parts is None:
            return (RouteTargetType.OBJECT, self)
        return (RouteTargetType.PARTS, parts)

    def is_surface(self, include_parts=False, ignore_deco_slots=False):
        key = (include_parts, ignore_deco_slots)
        is_surface = self._is_surface.get(key)
        if is_surface is not None:
            return is_surface
        if self.inventory_component is not None:
            self._is_surface[key] = True
            return True

        def is_valid_surface_slot(slot_type):
            if (not ignore_deco_slots or not slot_type.is_deco_slot) and slot_type.is_surface:
                return True
            return False

        for runtime_slot in self.get_runtime_slots_gen():
            if not include_parts and runtime_slot.owner is not self:
                pass
            if not any(is_valid_surface_slot(slot_type) for slot_type in runtime_slot.slot_types):
                pass
            if not runtime_slot.owner.is_same_object_or_part(self):
                pass
            self._is_surface[key] = True
        self._is_surface[key] = False
        return False

    def get_save_lot_coords_and_level(self):
        lot_coord_msg = LotCoord()
        parent = self.parent
        if parent is not None and parent.is_sim:
            parent.force_update_routing_location()
            starting_position = parent.position + parent.forward
            search_flags = CarryingObject.SNAP_TO_GOOD_LOCATION_SEARCH_FLAGS
            (trans, orient) = placement.find_good_location(placement.FindGoodLocationContext(starting_position=starting_position, starting_orientation=parent.orientation, starting_routing_surface=self.location.world_routing_surface, object_footprints=(self.footprint,), object_id=self.id, search_flags=search_flags))
            if trans is None:
                logger.warn('Unable to find good location to save object{}, which is parented to sim {} and cannot go into an inventory. Defaulting to location of sim.', self, parent)
                transform = parent.transform
            else:
                transform = sims4.math.Transform(trans, orient)
            transform = services.current_zone().lot.convert_to_lot_coordinates(transform)
        elif self.persistence_group == PersistenceGroups.OBJECT:
            transform = services.current_zone().lot.convert_to_lot_coordinates(self.transform)
        else:
            transform = self.transform
        lot_coord_msg.x = transform.translation.x
        lot_coord_msg.y = transform.translation.y
        lot_coord_msg.z = transform.translation.z
        lot_coord_msg.rot_x = transform.orientation.x
        lot_coord_msg.rot_y = transform.orientation.y
        lot_coord_msg.rot_z = transform.orientation.z
        lot_coord_msg.rot_w = transform.orientation.w
        if self.location.world_routing_surface is not None:
            level = self.location.level
        else:
            level = 0
        return (lot_coord_msg, level)

    def save_object(self, object_list, *args):
        save_data = super().save_object(object_list, *args)
        if save_data is None:
            return
        save_data.slot_id = self.bone_name_hash
        (save_data.position, save_data.level) = self.get_save_lot_coords_and_level()
        save_data.scale = self.scale
        save_data.state_index = self.state_index
        save_data.cost = self.current_value
        save_data.ui_metadata = self.ui_metadata._value
        save_data.is_new = self.new_in_inventory
        self.populate_icon_canvas_texture_info(save_data)
        if self._household_owner_id is not None:
            save_data.owner_id = self._household_owner_id
        save_data.needs_depreciation = self._needs_depreciation
        save_data.needs_post_bb_fixup = self._needs_post_bb_fixup
        save_data.created_from_lot_template = False
        save_data.stack_sort_order = self.get_stack_sort_order()
        if self.material_state:
            save_data.material_state = self.material_state.state_name_hash
        if self.geometry_state:
            save_data.geometry_state = self.geometry_state
        if self.model:
            model_key = sims4.resources.get_protobuff_for_key(self.model)
            save_data.model_override_resource_key = model_key
        if self.lighting_component:
            if self.lighting_component.light_color:
                (save_data.light_color.x, save_data.light_color.y, save_data.light_color.z, _) = sims4.color.to_rgba(self.lighting_component.light_color)
            save_data.light_dimmer_value = self.lighting_component.light_dimmer
        parent = self.parent
        if parent is not None and not parent.is_sim:
            save_data.parent_id = parent.id
        if parent is None or not parent.is_sim:
            save_data.object_parent_type = self._parent_type
            save_data.encoded_parent_location = self._parent_location
        self.save_unique_inventory_objects(save_data)
        return save_data

    def load_object(self, object_data):
        if object_data.HasField('owner_id'):
            self._household_owner_id = object_data.owner_id
        self.current_value = object_data.cost
        self.new_in_inventory = object_data.is_new
        super().load_object(object_data)
        if object_data.HasField('needs_depreciation'):
            self._needs_depreciation = object_data.needs_depreciation
        if object_data.HasField('needs_post_bb_fixup'):
            self._needs_post_bb_fixup = object_data.needs_post_bb_fixup
        else:
            self._needs_post_bb_fixup = self._needs_depreciation
        self.load_unique_inventory_objects(object_data)

    def finalize(self, **kwargs):
        super().finalize(**kwargs)
        self.try_post_bb_fixup(**kwargs)

    def set_household_owner_id(self, new_owner_id):
        if self.objectage_component is None and self.manager is not None:
            household_manager = services.household_manager()
            household_manager.decrement_household_object_count(self._household_owner_id)
            household_manager.increment_household_object_count(new_owner_id)
        self._household_owner_id = new_owner_id

    def get_household_owner_id(self):
        return self._household_owner_id

    def get_object_property(self, property_type):
        if property_type == GameObjectProperty.CATALOG_PRICE:
            return self.definition.price
        if property_type == GameObjectProperty.MODIFIED_PRICE:
            return self.current_value
        if property_type == GameObjectProperty.RARITY:
            return self.get_object_rarity()
        if property_type == GameObjectProperty.GENRE:
            return Genre.get_genre_localized_string(self)
        logger.error('Requested property_type {} not found on game_object'.format(property_type), owner='camilogarcia')

    def update_ownership(self, sim, make_sim_owner=True):
        household_id = sim.household.id
        if self._household_owner_id != household_id:
            if self.ownable_component is not None:
                self.ownable_component.update_sim_ownership(None)
            self.set_household_owner_id(household_id)
        if make_sim_owner and self.ownable_component is not None:
            self.ownable_component.update_sim_ownership(sim.sim_id)

    @property
    def flammable(self):
        fire_service = services.get_fire_service()
        if fire_service is not None:
            return fire_service.is_object_flammable(self)
        return False

    def object_bounds_for_flammable_object(self, location=DEFAULT, fire_retardant_bonus=0.0):
        if location is DEFAULT:
            location = sims4.math.Vector2(self.position.x, self.position.z)
        radius = self.object_radius
        if self.fire_retardant:
            radius += fire_retardant_bonus
        object_bounds = sims4.geometry.QtCircle(location, radius)
        return object_bounds

    @property
    def is_set_as_head(self):
        if self.parent is None:
            return False
        if not self.parent.is_sim:
            return False
        if self.parent.current_object_set_as_head is None:
            return False
        parent_head = self.parent.current_object_set_as_head()
        if not self.is_same_object_or_part(parent_head):
            return False
        return True
Esempio n. 10
0
class ObjectPart(HasTunableReference,
                 metaclass=TunedInstanceMetaclass,
                 manager=services.object_part_manager()):
    INSTANCE_TUNABLES = {
        'supported_posture_types':
        TunablePostureTypeListSnippet(
            description=
            '\n            The postures supported by this part. If empty, assumes all postures\n            are supported.\n            '
        ),
        'supported_affordance_data':
        TunableTuple(
            description=
            '\n            Define affordance compatibility for this part.\n            ',
            compatibility=TunableAffordanceFilterSnippet(
                description=
                '\n                Affordances supported by the part\n                '
            ),
            consider_mixers=Tunable(
                description=
                '\n                If checked, mixers are filtered through this compatibility\n                check. If unchecked, all mixers are assumed to be valid to run\n                on this part.\n                ',
                tunable_type=bool,
                default=False)),
        'blacklisted_buffs':
        TunableList(
            description=
            '\n            A list of buffs that will disable this part as a candidate to run an\n            interaction.\n            ',
            tunable=TunableReference(
                description=
                '\n               Reference to a buff to disable the part.\n               ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.BUFF),
                pack_safe=True)),
        'trait_requirements':
        TunableWhiteBlackList(
            description=
            '\n            Trait blacklist and whitelist requirements to pick this part.\n            ',
            tunable=Trait.TunableReference(
                description=
                '\n               Reference to the trait white/blacklists.\n               ',
                pack_safe=True)),
        'subroot':
        TunableReference(
            description=
            '\n            The reference of the subroot definition in the part.\n            ',
            manager=services.subroot_manager(),
            allow_none=True),
        'portal_data':
        TunableSet(
            description=
            '\n            If the object owning this part has a portal component tuned, the\n            specified portals will be created for each part of this type. The\n            root position of the part is the subroot position.\n            ',
            tunable=TunablePortalReference(pack_safe=True)),
        'can_pick':
        Tunable(
            description=
            '\n            If checked, this part can be picked (selected as target when\n            clicking on object.)  If unchecked, cannot be picked.\n            ',
            tunable_type=bool,
            default=True),
        'part_surface':
        TunableVariant(
            description=
            '\n            The rules to determine the surface type for this object.\n            ',
            part_owner=_PartOwnerSurfaceType.TunableFactory(),
            override_surface=_OverrideSurfaceType.TunableFactory(),
            default='part_owner')
    }
    _bone_name_hashes_for_part_suffices = None

    @classmethod
    def register_tuned_animation(cls, *_, **__):
        pass

    @classmethod
    def add_auto_constraint(cls, participant_type, tuned_constraint, **kwargs):
        pass

    @classmethod
    def get_bone_name_hashes_for_part_suffix(cls, part_suffix):
        if cls._bone_name_hashes_for_part_suffices is None:
            cls._bone_name_hashes_for_part_suffices = {}
        if part_suffix in cls._bone_name_hashes_for_part_suffices:
            return cls._bone_name_hashes_for_part_suffices[part_suffix]
        bone_name_hashes = set()
        if cls.subroot is not None:
            for bone_name_hash in cls.subroot.bone_names:
                if part_suffix is not None:
                    bone_name_hash = hash32(str(part_suffix),
                                            initial_hash=bone_name_hash)
                bone_name_hashes.add(bone_name_hash)
        cls._bone_name_hashes_for_part_suffices[part_suffix] = frozenset(
            bone_name_hashes)
        return cls._bone_name_hashes_for_part_suffices[part_suffix]
Esempio n. 11
0
class AutonomyModifier(BaseGameEffectModifier):
    STATISTIC_RESTRICTIONS = (statistics.commodity.Commodity,
                              statistics.statistic.Statistic,
                              statistics.skill.Skill, LifeSkillStatistic,
                              'RankedStatistic')
    ALWAYS_WHITELISTED_AFFORDANCES = TunableAffordanceFilterSnippet(
        description=
        '\n        Any affordances tuned to be compatible with this filter will always be\n        allowed. This is useful for stuff like death and debug interactions,\n        which should never be disallowed by an autonomy modifier.\n        '
    )

    @staticmethod
    def _verify_tunable_callback(instance_class, tunable_name, source, value):
        for (situation_type, multiplier
             ) in value.situation_type_social_score_multiplier.items():
            if multiplier == 1:
                logger.error(
                    'A situation type social score multiplier currently has a tuned multiplier of 1. This is invalid, please change to a value other than 1 or delete the entry. Class: {} Situation Type: {}',
                    instance_class, situation_type)

    FACTORY_TUNABLES = {
        'verify_tunable_callback':
        _verify_tunable_callback,
        'description':
        "\n            An encapsulation of a modification to Sim behavior.  These objects\n            are passed to the autonomy system to affect things like scoring,\n            which SI's are available, etc.\n            ",
        'provided_affordance_compatibility':
        TunableAffordanceFilterSnippet(
            description=
            '\n            Tune this to provide suppression to certain affordances when an object has\n            this autonomy modifier.\n            EX: Tune this to exclude all on the buff for the maid to prevent\n                other sims from trying to chat with the maid while the maid is\n                doing her work.\n            To tune if this restriction is for autonomy only, etc, see\n            super_affordance_suppression_mode.\n            Note: This suppression will also apply to the owning sim! So if you\n                prevent people from autonomously interacting with the maid, you\n                also prevent the maid from doing self interactions. To disable\n                this, see suppress_self_affordances.\n            '
        ),
        'super_affordance_suppression_mode':
        TunableEnumEntry(
            description=
            '\n            Setting this defines how to apply the settings tuned in Super Affordance Compatibility.',
            tunable_type=SuperAffordanceSuppression,
            default=SuperAffordanceSuppression.AUTONOMOUS_ONLY),
        'super_affordance_suppress_on_add':
        Tunable(
            description=
            '\n            If checked, then the suppression rules will be applied when the\n            modifier is added, potentially canceling interactions the owner is\n            running.\n            ',
            tunable_type=bool,
            default=False),
        'suppress_self_affordances':
        Tunable(
            description=
            "\n            If checked, the super affordance compatibility tuned for this \n            autonomy modifier will also apply to the sim performing self\n            interactions.\n            \n            If not checked, we will not do provided_affordance_compatibility checks\n            if the target of the interaction is the same as the actor.\n            \n            Ex: Tune the maid's provided_affordance_compatibility to exclude all\n                so that other sims will not chat with the maid. But disable\n                suppress_self_affordances so that the maid can still perform\n                interactions on herself (such as her No More Work interaction\n                that tells her she's finished cleaning).\n            ",
            tunable_type=bool,
            default=True),
        'score_multipliers':
        TunableMapping(
            description=
            '\n                Mapping of statistics to multipliers values to the autonomy\n                scores.  EX: giving motive_bladder a multiplier value of 2 will\n                make it so that that motive_bladder is scored twice as high as\n                it normally would be.\n                ',
            key_type=TunableReference(
                services.get_instance_manager(sims4.resources.Types.STATISTIC),
                class_restrictions=STATISTIC_RESTRICTIONS,
                description=
                '\n                    The stat the multiplier will apply to.\n                    '
            ),
            value_type=Tunable(
                float,
                1,
                description=
                '\n                    The autonomy score multiplier for the stat.  Multiplies\n                    autonomy scores by the tuned value.\n                    '
            )),
        'static_commodity_score_multipliers':
        TunableMapping(
            description=
            '\n                Mapping of statistics to multipliers values to the autonomy\n                scores.  EX: giving motive_bladder a multiplier value of 2 will\n                make it so that that motive_bladder is scored twice as high as\n                it normally would be.\n                ',
            key_type=TunableReference(
                description=
                '\n                    The static commodity the multiplier will apply to.\n                    ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.STATIC_COMMODITY),
                pack_safe=True),
            value_type=Tunable(
                float,
                1,
                description=
                '\n                    The autonomy score multiplier for the static commodity.  Multiplies\n                    autonomy scores by the tuned value.\n                    '
            )),
        'relationship_score_multiplier_with_buff_on_target':
        TunableMapping(
            description=
            "\n                Mapping of buffs to multipliers.  The buff must exist on the TARGET sim.\n                If it does, this value will be multiplied into the relationship score.\n                \n                Example: The make children desire to socialize with children, you can add \n                this autonomy modifier to the child's age buff.  You can then map it with \n                a key to the child buff to apply a positive multiplier.  An alternative \n                would be to create a mapping to every other age and apply a multiplier that \n                is smaller than 1.\n                ",
            key_type=TunableReference(
                services.get_instance_manager(sims4.resources.Types.BUFF),
                description=
                '\n                    The buff that the target sim must have to apply this multiplier.\n                    '
            ),
            value_type=Tunable(
                float,
                1,
                description=
                '\n                    The multiplier to apply.\n                    '
            )),
        'locked_stats':
        TunableList(
            TunableReference(
                services.get_instance_manager(sims4.resources.Types.STATISTIC),
                class_restrictions=STATISTIC_RESTRICTIONS,
                pack_safe=True,
                description=
                '\n                    The stat the modifier will apply to.\n                    '
            ),
            description=
            '\n                List of the stats we locked from this modifier.  Locked stats\n                are set to their maximum values and then no longer allowed to\n                decay.\n                '
        ),
        'locked_stats_autosatisfy_on_unlock':
        Tunable(
            description=
            '\n            If true, locked stats will be set to the value on the auto\n            satisfy curve when unlocked.  If false they will remain as-is.\n            (i.e. maxed)\n            ',
            tunable_type=bool,
            default=True),
        'decay_modifiers':
        CommodityDecayModifierMapping(
            description=
            '\n                Statistic to float mapping for decay modifiers for\n                statistics.  All decay modifiers are multiplied together along\n                with the decay rate.\n                '
        ),
        'decay_modifier_by_category':
        StatisticCategoryModifierMapping(
            description=
            '\n                Statistic Category to float mapping for decay modifiers for\n                statistics. All decay modifiers are multiplied together along with\n                decay rate.\n                '
        ),
        'skill_tag_modifiers':
        TunableMapping(
            description=
            '\n                The skill_tag to float mapping of skill modifiers.  Skills with\n                these tags will have their amount gained multiplied by the\n                sum of all the tuned values.\n                ',
            key_type=TunableEnumEntry(
                tag.Tag,
                tag.Tag.INVALID,
                description=
                '\n                    What skill tag to apply the modifier on.\n                    '
            ),
            value_type=Tunable(float, 0)),
        'commodities_to_add':
        TunableList(
            TunableReference(
                services.get_instance_manager(sims4.resources.Types.STATISTIC),
                class_restrictions=statistics.commodity.Commodity,
                pack_safe=True),
            description=
            '\n                Commodites that are added while this autonomy modifier is\n                active.  These commodities are removed when the autonomy\n                modifier is removed.\n                '
        ),
        'commodities_value_persist_for_travel':
        Tunable(
            description=
            '\n            If checked, value of commodities added from this modifier will persist\n            through travel. This only works for commodities those have "Persisted Tuning"\n            checked. Also please note commodities themselves won\'t persist, we record\n            their value before travel and set the value back once arrived the destination.\n            \n            If not checked, value of commodities added from this modifier will not persist\n            through travel. Commodities will be re-added and start from their initial value.\n            \n            Ex: Check this for Buff_Trait_Loner\'s autonomy modifier will make its attached\n            Commodity_Trait_Loner_Solitude\'s value persist through travel. So when the sim\n            arrives another lot, the commodity\'s value will be set back to that just before\n            the travel, so its "Happy Loner" buff can persist as a consequence.\n            ',
            tunable_type=bool,
            default=False),
        'only_scored_stats':
        OptionalTunable(
            TunableList(
                TunableReference(
                    services.get_instance_manager(
                        sims4.resources.Types.STATISTIC),
                    class_restrictions=STATISTIC_RESTRICTIONS),
                description=
                '\n                    List of statistics that will only be considered when doing\n                    autonomy.\n                    '
            ),
            tuning_filter=FilterTag.EXPERT_MODE,
            description=
            "\n                If enabled, the sim in this role state will consider ONLY these\n                stats when doing autonomy. EX: for the maid, only score\n                commodity_maidrole_clean so she doesn't consider doing things\n                that she shouldn't care about.\n                "
        ),
        'only_scored_static_commodities':
        OptionalTunable(
            TunableList(
                StaticCommodity.TunableReference(),
                description=
                '\n                    List of statistics that will only be considered when doing\n                    autonomy.\n                    '
            ),
            tuning_filter=FilterTag.EXPERT_MODE,
            description=
            '\n                If enabled, the sim in this role state will consider ONLY these\n                static commodities when doing autonomy. EX: for walkbys, only\n                consider the ringing the doorbell\n                '
        ),
        'stat_use_multiplier':
        TunableMapping(
            description=
            '\n                List of stats and multiplier to affect their increase-decrease.\n                All stats on this list whenever they get modified (e. by a \n                constant modifier on an interaction, an interaction result...)\n                will apply the multiplier to their modified values. \n                e. A toilet can get a multiplier to decrease the repair rate\n                when its used, for this we would tune the commodity\n                brokenness and the multiplier 0.5 (to decrease its effect)\n                This tunable multiplier will affect the object statistics\n                not the ones for the sims interacting with it.\n                ',
            key_type=TunableReference(
                description=
                '\n                    The stat the multiplier will apply to.\n                    ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.STATISTIC),
                class_restrictions=STATISTIC_RESTRICTIONS,
                pack_safe=True),
            value_type=TunableTuple(
                description=
                '\n                    Float value to apply to the statistic whenever its\n                    affected.  Greater than 1.0 if you want to increase.\n                    Less than 1.0 if you want a decrease (>0.0). \n                    A value of 0 is considered invalid and is skipped.\n                    ',
                multiplier=Tunable(
                    description=
                    '\n                        Float value to apply to the statistic whenever its\n                        affected.  Greater than 1.0 if you want to increase.\n                        Less than 1.0 if you want a decrease (>0.0). \n                        A value of 0 is considered invalid and is skipped.\n                        ',
                    tunable_type=float,
                    default=1.0),
                apply_direction=TunableEnumEntry(
                    description=
                    '\n                        Direction on when the multiplier should work on the \n                        statistic.  For example a decrease on an object \n                        brokenness rate, should not increase the time it takes to \n                        repair it.\n                        ',
                    tunable_type=StatisticChangeDirection,
                    default=StatisticChangeDirection.BOTH))),
        'relationship_multipliers':
        TunableMapping(
            description=
            '\n                List of relationship tracks and multiplier to affect their\n                increase or decrease of track value. All stats on this list\n                whenever they get modified (e. by a constant modifier on an\n                interaction, an interaction result...) will apply the\n                multiplier to their modified values. e.g. A LTR_Friendship_Main\n                can get a multiplier to decrease the relationship decay when\n                interacting with someone with a given trait, for this we would\n                tune the relationship track LTR_Friendship_Main and the\n                multiplier 0.5 (to decrease its effect)\n                ',
            key_type=relationships.relationship_track.RelationshipTrack.
            TunableReference(
                description=
                '\n                    The Relationship track the multiplier will apply to.\n                    '
            ),
            value_type=TunableTuple(
                description=
                "\n                    Float value to apply to the statistic whenever it's\n                    affected.  Greater than 1.0 if you want to increase.\n                    Less than 1.0 if you want a decrease (>0.0).\n                    ",
                multiplier=Tunable(tunable_type=float, default=1.0),
                apply_direction=TunableEnumEntry(
                    description=
                    '\n                        Direction on when the multiplier should work on the \n                        statistic.  For example a decrease on an object \n                        brokenness rate, should not increase the time it takes to \n                        repair it.\n                        ',
                    tunable_type=StatisticChangeDirection,
                    default=StatisticChangeDirection.BOTH))),
        'object_tags_that_override_off_lot_autonomy':
        TunableList(
            description=
            "\n                A list of object tags for objects that are always valid to be considered \n                for autonomy regardless of their on-lot or off-lot status.  Note that this \n                will only override off-lot autonomy availability.  It doesn't affect other \n                ways that objects are culled out.  For example, if an object list is passed\n                into the autonomy request (like when we're looking at targets of a crafting \n                phase), we only consider the objects in that list.  This won't override that \n                list.\n            ",
            tunable=TunableEnumEntry(tunable_type=tag.Tag,
                                     default=tag.Tag.INVALID)),
        'off_lot_autonomy_rule':
        OptionalTunable(tunable=TunableOffLotAutonomy()),
        'override_convergence_value':
        OptionalTunable(
            description=
            "\n            If enabled it will set a new convergence value to the tuned\n            statistics.  The decay of those statistics will start moving\n            toward the new convergence value.\n            Convergence value will apply as long as these modifier is active,\n            when modifier is removed, convergence value will return to default\n            tuned value.\n            As a tuning restriction when this modifier gets removed we will \n            reset the convergence to its original value.  This means that we \n            don't support two states at the same time overwriting convergence\n            so we should'nt tune multiple convergence overrides on the same \n            object.\n            ",
            tunable=TunableMapping(
                description=
                '\n                Mapping of statistic to new convergence value.\n                ',
                key_type=Commodity.TunableReference(),
                value_type=Tunable(
                    description=
                    '\n                    Value to which the statistic should convert to.\n                    ',
                    tunable_type=int,
                    default=0)),
            disabled_name='Use_default_convergence',
            enabled_name='Set_new_convergence_value'),
        'subject':
        TunableVariant(
            description=
            '\n            Specifies to whom this autonomy modifier will apply.\n            - Apply to owner: Will apply the modifiers to the object or sim who \n            is triggering the modifier.  \n            e.g Buff will apply the modifiers to the sim when he gets the buff.  \n            An object will apply the modifiers to itself when it hits a state.\n            - Apply to interaction participant:  Will save the modifiers to \n            be only triggered when the object/sim who holds the modifier \n            is on an interaction.  When the interaction starts the the subject\n            tuned will get the modifiers during the duration of the interaction. \n            e.g A sim with modifiers to apply on an object will only trigger \n            when the sim is interactin with an object.\n            ',
            apply_on_interaction_to_participant=OptionalTunable(
                TunableEnumFlags(
                    description=
                    '\n                    Subject on which the modifiers should apply.  When this is set\n                    it will mean that the autonomy modifiers will trigger on a \n                    subect different than the object where they have been added.\n                    e.g. a shower ill have hygiene modifiers that have to affect \n                    the Sim ',
                    enum_type=ParticipantType,
                    default=ParticipantType.Object)),
            default='apply_to_owner',
            locked_args={'apply_to_owner': False}),
        'suppress_preroll_autonomy':
        Tunable(
            description=
            '\n            If checked, sims with this buff will not run preroll autonomy when\n            first loading into a lot. This means that when the loading screen\n            disappears, they will be standing exactly where they spawned,\n            looking like a chump, instead of being somewhere on the lot doing\n            a normal-looking activity. As soon as the loading screen disappears,\n            all bets are off and autonomy will run normally again.\n            ',
            tunable_type=bool,
            default=False),
        'situation_type_social_score_multiplier':
        TunableMapping(
            description=
            '\n                A tunable mapping form situation type to multiplier to apply\n                when the target Sim is in a situation of the specified type with\n                the actor Sim.\n                ',
            key_type=TunableReference(
                description=
                '\n                    A reference to the type of situation that both Sims need to\n                    be in together in order for the multiplier to be applied.\n                    ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.SITUATION),
                pack_safe=True),
            value_type=Tunable(
                description=
                '\n                    The multiplier to apply.\n                    ',
                tunable_type=float,
                default=1)),
        'transition_from_sit_posture_penalty':
        OptionalTunable(
            description=
            '\n            When enabled causes the Sim to be penalized for transitioning\n            from Sit to another posture.\n            ',
            tunable=Tunable(
                description=
                '\n                The multiplier to apply to the autonomous interaction score\n                as a result of the Sim transitioning from sit to something\n                else.\n                \n                This number should be less than one (<1) in order for it to be\n                a penalty, otherwise it will be a bonus.\n                ',
                tunable_type=float,
                default=1.0)),
        'supress_outside_objects_if_sun_out':
        OptionalTunable(
            description=
            "\n            When enabled, objects on the outside will be suppressed by autonomy\n            if the sun is out (i.e. region provides light, it's daytime, \n            and weather isn't too cloudy) and will not be used unless they have\n            interactions tagged with 'counts_as_inside'.\n            ",
            tunable=Tunable(
                description=
                '\n                When checked, outside objects will be suppressed, otherwise\n                supression will be canceled.\n                Canceling suppression will have a higher priority than an\n                active supression, this is to support cases like vampire buffs\n                always being suppressed, but when they activate the daywalker\n                power, that cancelation of suppression should always have a \n                higher priority. \n                ',
                tunable_type=bool,
                default=True)),
        'outside_objects_multiplier':
        OptionalTunable(
            description=
            "\n            When enabled, objects that are outside will have their interaction \n            scores modified unless they are tagged with 'counts_as_inside'.\n            ",
            tunable=Tunable(
                description=
                '\n                Amount to multiple the autonomy score by.\n                ',
                tunable_type=float,
                default=1.0)),
        'interaction_score_modifier':
        TunableList(
            description=
            '\n            A list of score modifications to interactions (specified by list, \n            affordance or tags) to apply when the actor has this autonomy modifier.\n            ',
            tunable=TunableTuple(
                modifier=Tunable(
                    description=
                    '\n                    Multiply score by this amount.\n                    ',
                    tunable_type=float,
                    default=1.0),
                affordances=TunableSet(
                    description=
                    '\n                    A list of affordances that will be compared against.\n                    ',
                    tunable=TunableReference(
                        services.get_instance_manager(
                            sims4.resources.Types.INTERACTION))),
                affordance_lists=TunableList(
                    description=
                    '\n                    A list of affordance snippets that will be compared against.\n                    ',
                    tunable=snippets.TunableAffordanceListReference()),
                interaction_category_tags=tag.TunableTags(
                    description=
                    '\n                    This attribute is used to test for affordances that contain any of the tags in this set.\n                    ',
                    filter_prefixes=('interaction', ))))
    }

    def __init__(self,
                 score_multipliers=None,
                 static_commodity_score_multipliers=None,
                 relationship_score_multiplier_with_buff_on_target=None,
                 provided_affordance_compatibility=None,
                 super_affordance_suppression_mode=SuperAffordanceSuppression.
                 AUTONOMOUS_ONLY,
                 suppress_self_affordances=False,
                 suppress_preroll_autonomy=False,
                 super_affordance_suppress_on_add=False,
                 locked_stats=None,
                 locked_stats_autosatisfy_on_unlock=None,
                 decay_modifiers=None,
                 statistic_modifiers=None,
                 skill_tag_modifiers=None,
                 commodities_to_add=(),
                 commodities_value_persist_for_travel=False,
                 only_scored_stats=None,
                 only_scored_static_commodities=None,
                 stat_use_multiplier=None,
                 relationship_multipliers=None,
                 object_tags_that_override_off_lot_autonomy=None,
                 off_lot_autonomy_rule=None,
                 override_convergence_value=None,
                 subject=None,
                 exclusive_si=None,
                 decay_modifier_by_category=None,
                 situation_type_social_score_multiplier=None,
                 transition_from_sit_posture_penalty=None,
                 supress_outside_objects_if_sun_out=None,
                 outside_objects_multiplier=None,
                 interaction_score_modifier=None):
        self._provided_affordance_compatibility = provided_affordance_compatibility
        self._super_affordance_suppression_mode = super_affordance_suppression_mode
        self._suppress_self_affordances = suppress_self_affordances
        self._super_affordance_suppress_on_add = super_affordance_suppress_on_add
        self._score_multipliers = score_multipliers
        self._locked_stats = tuple(
            set(locked_stats)) if locked_stats is not None else None
        self.autosatisfy_on_unlock = locked_stats_autosatisfy_on_unlock
        self._decay_modifiers = decay_modifiers
        self._decay_modifier_by_category = decay_modifier_by_category
        self._statistic_modifiers = statistic_modifiers
        self._relationship_score_multiplier_with_buff_on_target = relationship_score_multiplier_with_buff_on_target
        self._skill_tag_modifiers = skill_tag_modifiers
        self._commodities_to_add = commodities_to_add
        self._commodities_value_persist_for_travel = commodities_value_persist_for_travel
        self._stat_use_multiplier = stat_use_multiplier
        self._relationship_multipliers = relationship_multipliers
        self._object_tags_that_override_off_lot_autonomy = object_tags_that_override_off_lot_autonomy
        self._off_lot_autonomy_rule = off_lot_autonomy_rule
        self._subject = subject
        self._override_convergence_value = override_convergence_value
        self._exclusive_si = exclusive_si
        self.suppress_preroll_autonomy = suppress_preroll_autonomy
        self._situation_type_social_score_multiplier = situation_type_social_score_multiplier
        self.transition_from_sit_posture_penalty = transition_from_sit_posture_penalty
        self.supress_outside_objects = supress_outside_objects_if_sun_out
        self.outside_objects_multiplier = outside_objects_multiplier
        self._interaction_score_modifier = interaction_score_modifier
        self._skill_tag_modifiers = {}
        if skill_tag_modifiers:
            for (skill_tag, skill_tag_modifier) in skill_tag_modifiers.items():
                skill_modifier = SkillTagMultiplier(
                    skill_tag_modifier, StatisticChangeDirection.INCREASE)
                self._skill_tag_modifiers[skill_tag] = skill_modifier
        if static_commodity_score_multipliers:
            if self._score_multipliers is not None:
                self._score_multipliers = FrozenAttributeDict(
                    self._score_multipliers,
                    dict(static_commodity_score_multipliers))
            else:
                self._score_multipliers = static_commodity_score_multipliers
        self._static_commodity_score_multipliers = static_commodity_score_multipliers
        self._only_scored_stat_types = None
        if only_scored_stats is not None:
            self._only_scored_stat_types = []
            self._only_scored_stat_types.extend(only_scored_stats)
        if only_scored_static_commodities is not None:
            if self._only_scored_stat_types is None:
                self._only_scored_stat_types = []
            self._only_scored_stat_types.extend(only_scored_static_commodities)
        super().__init__(GameEffectType.AUTONOMY_MODIFIER)

    def apply_modifier(self, sim_info):
        return sim_info.add_statistic_modifier(self)

    def remove_modifier(self, sim_info, handle):
        sim_info.remove_statistic_modifier(handle)

    def __repr__(self):
        return standard_auto_repr(self)

    @property
    def exclusive_si(self):
        return self._exclusive_si

    def affordance_suppressed(self,
                              sim,
                              aop_or_interaction,
                              user_directed=DEFAULT):
        user_directed = aop_or_interaction.is_user_directed if user_directed is DEFAULT else user_directed
        if not self._suppress_self_affordances and aop_or_interaction.target is sim:
            return False
        affordance = aop_or_interaction.affordance
        if self._provided_affordance_compatibility is None:
            return False
        if user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.AUTONOMOUS_ONLY:
            return False
        if not user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.USER_DIRECTED:
            return False
        if self._provided_affordance_compatibility(affordance):
            return False
        elif self.ALWAYS_WHITELISTED_AFFORDANCES(affordance):
            return False
        return True

    def locked_stats_gen(self):
        if self._locked_stats is not None:
            yield from self._locked_stats

    def get_score_multiplier(self, stat_type):
        if self._score_multipliers is not None and stat_type in self._score_multipliers:
            return self._score_multipliers[stat_type]
        return 1

    def get_stat_multiplier(self, stat_type, participant_type):
        if self._stat_use_multiplier is None:
            return 1
        elif self._subject == participant_type and stat_type in self._stat_use_multiplier:
            return self._stat_use_multiplier[stat_type].multiplier
        return 1

    @property
    def subject(self):
        return self._subject

    @property
    def statistic_modifiers(self):
        return self._statistic_modifiers

    @property
    def statistic_multipliers(self):
        return self._stat_use_multiplier

    @property
    def relationship_score_multiplier_with_buff_on_target(self):
        return self._relationship_score_multiplier_with_buff_on_target

    @property
    def situation_type_social_score_multiplier(self):
        return self._situation_type_social_score_multiplier

    @property
    def relationship_multipliers(self):
        return self._relationship_multipliers

    @property
    def decay_modifiers(self):
        return self._decay_modifiers

    @property
    def decay_modifier_by_category(self):
        return self._decay_modifier_by_category

    @property
    def skill_tag_modifiers(self):
        return self._skill_tag_modifiers

    @property
    def commodities_to_add(self):
        return self._commodities_to_add

    @property
    def commodities_value_persist_for_travel(self):
        return self._commodities_value_persist_for_travel

    @property
    def override_convergence(self):
        return self._override_convergence_value

    def is_locked(self, stat_type):
        if self._locked_stats is None:
            return False
        return stat_type in self._locked_stats

    def is_scored(self, stat_type):
        if self._only_scored_stat_types is None or stat_type in self._only_scored_stat_types:
            return True
        return False

    @property
    def object_tags_that_override_off_lot_autonomy(self):
        return self._object_tags_that_override_off_lot_autonomy

    @property
    def off_lot_autonomy_rule(self):
        return self._off_lot_autonomy_rule

    @property
    def super_affordance_suppress_on_add(self):
        return self._super_affordance_suppress_on_add

    @property
    def interaction_score_modifier(self):
        return self._interaction_score_modifier
Esempio n. 12
0
class GameObject(ClientObjectMixin, ReservationMixin, ScriptObject, reset.ResettableObjectMixin):
    INSTANCE_TUNABLES = {'_transient_tuning': Tunable(description='\n            If transient the object will always be destroyed and never put down.\n            ', tunable_type=bool, default=False, tuning_filter=FilterTag.EXPERT_MODE, display_name='Transient'), 'additional_interaction_constraints': TunableList(description='\n            A list of constraints that must be fulfilled in order to run the \n            linked affordances. This should only be used when the same \n            affordance uses different constraints based on the object.\n            ', tunable=TunableTuple(constraint=TunableConstraintVariant(description='\n                    A constraint that must be fulfilled in order to interact with this object.\n                    '), affordance_links=TunableAffordanceFilterSnippet()), tuning_filter=FilterTag.EXPERT_MODE), 'autonomy_modifiers': TunableList(description='\n            List of autonomy modifiers that will be applied to the tuned\n            participant type.  These can be used to tune object variations.\n            ', tunable=TunableAutonomyModifier(locked_args={'commodities_to_add': (), 'score_multipliers': frozendict(), 'provided_affordance_compatibility': None, 'super_affordance_suppression_mode': autonomy.autonomy_modifier.SuperAffordanceSuppression.AUTONOMOUS_ONLY, 'suppress_self_affordances': False, 'only_scored_static_commodities': None, 'only_scored_stats': None, 'relationship_multipliers': None})), 'set_ico_as_carry_target': Tunable(description="\n            Whether or not the crafting process should set the carry target\n            to be the ICO.  Example Usage: Sheet Music has this set to false\n            because the sheet music is in the Sim's inventory and the Sim needs\n            to carry the guitar/violin.  This is a tunable on game object\n            because the ICO in the crafting process can be any game object.\n            ", tunable_type=bool, default=True), 'supported_posture_types': TunablePostureTypeListSnippet(description='\n            The postures supported by this part. If empty, assumes all postures \n            are supported.\n            '), '_add_to_posture_graph_if_parented': Tunable(description="\n            Whether or not the object should be added to the posture graph if it\n            is parented to an object that isn't a surface.  i.e. chairs that\n            should be seatable when slotted into a campfire (which isn't a surface)\n            ", tunable_type=bool, default=False), 'allow_preroll_multiple_targets': Tunable(description='\n            When checked allows multiple sims to target this object during \n            preroll autonomy. If not checked then the default preroll behavior\n            will happen.\n            \n            The default setting is to only allow each target to be targeted\n            once during preroll. However it makes sense in certain cases where\n            multiple sims can use the same object at the same time to allow\n            multiple targets.\n            ', tunable_type=bool, default=False), 'icon_override': OptionalTunable(description='\n            If enabled, the icon that will be displayed in the UI for this object.\n            This does not override the build/buy icon, which can be overriden\n            through the catalog.\n            ', tunable=TunableResourceKey(tuning_group=GroupNames.UI, resource_types=sims4.resources.CompoundTypes.IMAGE)), 'flammable_area': TunableFlammableAreaVariant(description='\n            How the object defines its area of flammability. This is used \n            by the fire service to build the quadtree of flammable objects.\n            '), '_provided_mobile_posture_affordances': OptionalTunable(description="\n            If enabled, this object will add these postures to the posture\n            graph. We need to do this for mobile postures that have no body\n            target and we don't intend on them ever being included in searches\n            for getting from one place to another without this object somewhere\n            on the lot.\n            ", tunable=TunableSet(description='\n                The set of mobile posture providing interactions we want this\n                object to provide.\n                ', tunable=TunableReference(description='\n                    The posture providing interaction we want to add to the\n                    posture graph when this object is instanced.\n                    ', manager=services.affordance_manager(), pack_safe=True, class_restrictions='SuperInteraction'), minlength=1)), 'recycling_data': TunableTuple(description='\n            Recycling information for this object.\n            ', recycling_values=TunableMapping(description='\n                Maps a buck type to the recycled value for this object.\n                ', key_type=TunableEnumEntry(tunable_type=BucksType, default=BucksType.INVALID, invalid_enums=BucksType.INVALID, pack_safe=True), key_name='Bucks Type', value_type=TunableRange(description='\n                    Object multiplier for this buck type.\n                    ', tunable_type=float, default=1.0, minimum=0.0), value_name='Value'), recycling_loot=TunableList(description='\n                Loot Actions that will be given when the object is recycled.\n                SingleActorAndObjectResolver will be used where actor is specified\n                by subject, and object is the object being recycled.\n                ', tunable=TunableReference(description='\n                    A loot action applied.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), pack_safe=True))), 'tests_to_bypass_utility_requirement': TunableMapping(description='\n            A mapping of utility types to tunable test sets. \n            ', key_type=TunableEnumEntry(tunable_type=Utilities, default=Utilities.POWER), value_type=TunableTestSet(description='\n                A test set to run when the object is the target of a\n                recipe or interaction and the utility required for that recipe or \n                interaction is absent. If at least one test group passes, \n                then any interaction or recipe that requires the utility\n                will be allowed to run despite the absence of the utility. \n                \n                ORs of ANDs.\n                '))}

    @classmethod
    def _verify_tuning_callback(cls):
        if cls._provided_mobile_posture_affordances is not None:
            for affordance in cls._provided_mobile_posture_affordances:
                if affordance.provided_posture_type is None:
                    logger.error("{} provides posture affordance {} but it doesn't provide a posture.", cls, affordance, owner='rmccord')
                elif not affordance.provided_posture_type.unconstrained:
                    logger.error('{} provides posture affordance {} but the provided posture is not unconstrained and therefore requires a body target.', cls, affordance, owner='rmccord')
                elif affordance in PostureGraphService.POSTURE_PROVIDING_AFFORDANCES:
                    logger.error('{} provides posture affordance {} but this is already provided by the posture graph in Posture Providing Affordances.', cls, affordance, owner='rmccord')

    def __init__(self, definition, **kwargs):
        super().__init__(definition, **kwargs)
        self._on_location_changed_callbacks = None
        self._transient = None
        self._created_constraints = None
        self._created_constraints_dirty = True
        self._household_owner_id = None
        self.new_in_inventory = True
        self.is_new_object = False
        self._provided_surface = UNSET
        zone = services.current_zone()
        account_id = build_buy.get_user_in_build_buy(zone.id)
        if account_id is not None:
            self.set_household_owner_id(zone.lot.owner_household_id)
            self.set_post_bb_fixup_needed()
            zone.set_to_fixup_on_build_buy_exit(self)
        self._hidden_flags = 0
        self._local_tags = None
        self._persisted_tags = None
        self._is_surface = None
        self._build_buy_use_flags = 0
        self._scheduled_elements = None
        self._work_locks = WeakSet()
        self._on_hidden_or_shown_callbacks = None

    def add_work_lock(self, handle):
        self._work_locks.add(handle)

    def remove_work_lock(self, handle):
        self._work_locks.discard(handle)

    @property
    def has_work_locks(self):
        if self._work_locks:
            return True
        return False

    @property
    def is_fire_related_object(self):
        if self.is_sim:
            return False
        return self.fire_retardant or self.flammable

    @constproperty
    def is_valid_for_height_checks():
        return True

    def has_tag(self, tag):
        if self._local_tags and tag in self._local_tags:
            return True
        if self._persisted_tags and tag in self._persisted_tags:
            return True
        return self.definition.has_build_buy_tag(tag)

    def has_any_tag(self, tags):
        return any(self.has_tag(tag) for tag in tags)

    def get_tags(self):
        tags = frozenset(self.definition.build_buy_tags)
        if self._local_tags:
            tags |= self._local_tags
        return tags

    def append_tags(self, tag_set, persist=False):
        if self.manager is not None:
            self.manager.add_tags_and_object_to_cache(tag_set, self)
        if self._local_tags:
            self._local_tags = self._local_tags | tag_set
        else:
            self._local_tags = tag_set
        if persist:
            if self._persisted_tags:
                self._persisted_tags = self._persisted_tags | tag_set
            else:
                self._persisted_tags = tag_set

    def get_icon_info_data(self):
        return IconInfoData(obj_instance=self, obj_def_id=self.definition.id, obj_geo_hash=self.geometry_state, obj_material_hash=self.material_hash, obj_name=LocalizationHelperTuning.get_object_name(self))

    @property
    def catalog_name(self):
        return get_object_catalog_name(self.definition.id)

    @property
    def catalog_description(self):
        return get_object_catalog_description(self.definition.id)

    def is_routing_surface_overlapped_at_position(self, position):
        routing_surface = self.provided_routing_surface
        if routing_surface is not None:
            (_, object_id) = services.terrain_service.terrain_object().get_routing_surface_height_and_surface_object_at(position.x, position.z, routing_surface)
            if object_id == self.id:
                return False
        return True

    @property
    def provided_routing_surface(self):
        if self._provided_surface is UNSET:
            self._provided_surface = None
            if self.routing_surface is not None:
                if self.has_component(FOOTPRINT_COMPONENT):
                    if placement.has_object_surface_footprint(self.get_footprint()):
                        self._provided_surface = routing.SurfaceIdentifier(services.current_zone_id(), self.routing_surface.secondary_id, routing.SurfaceType.SURFACETYPE_OBJECT)
        return self._provided_surface

    def get_icon_override(self):
        for icon_override in self._icon_override_gen():
            if icon_override is not None:
                return icon_override

    @forward_to_components_gen
    def _icon_override_gen(self):
        if self.icon_override is not None:
            yield self.icon_override

    @forward_to_components
    def populate_localization_token(self, token):
        self.definition.populate_localization_token(token)

    def is_hidden(self, allow_hidden_flags=0):
        if int(self._hidden_flags) & ~int(allow_hidden_flags):
            return True
        return False

    def has_hidden_flags(self, hidden_flags):
        if int(self._hidden_flags) & int(hidden_flags):
            return True
        return False

    def hide(self, hidden_reasons_to_add):
        self._hidden_flags = self._hidden_flags | hidden_reasons_to_add
        if self._on_hidden_or_shown_callbacks is not None:
            self._on_hidden_or_shown_callbacks(self, hidden_reasons_to_add, added=True)

    def show(self, hidden_reasons_to_remove):
        self._hidden_flags = self._hidden_flags & ~hidden_reasons_to_remove
        if self._on_hidden_or_shown_callbacks is not None:
            self._on_hidden_or_shown_callbacks(self, hidden_reasons_to_remove, added=False)

    @property
    def transient(self):
        if self._transient is not None:
            return self._transient
        return self._transient_tuning

    @transient.setter
    def transient(self, value):
        self._transient = value

    @distributor.fields.Field(op=distributor.ops.SetBuildBuyUseFlags)
    def build_buy_use_flags(self):
        return self._build_buy_use_flags

    @build_buy_use_flags.setter
    def build_buy_use_flags(self, value):
        self._build_buy_use_flags = value

    @distributor.fields.Field(op=distributor.ops.SetOwnerId)
    def household_owner_id(self):
        return self._household_owner_id

    _resend_household_owner_id = household_owner_id.get_resend()

    def get_edges(self):
        (lower_bound, upper_bound) = self.get_fooptrint_polygon_bounds()
        if lower_bound is None or upper_bound is None:
            return ()
        y = self.position.y
        transform = self.transform
        p0 = transform.transform_point(sims4.math.Vector3(lower_bound.x, y, lower_bound.z))
        p1 = transform.transform_point(sims4.math.Vector3(lower_bound.x, y, upper_bound.z))
        p2 = transform.transform_point(sims4.math.Vector3(upper_bound.x, y, upper_bound.z))
        p3 = transform.transform_point(sims4.math.Vector3(upper_bound.x, y, lower_bound.z))
        return ((p0, p1), (p1, p2), (p2, p3), (p3, p0))

    def get_edge_constraint(self, constraint_width=1.0, inward_dir=False, return_constraint_list=False, los_reference_point=DEFAULT, sim=None):
        edges = self.get_edges()
        polygons = []
        for (start, stop) in edges:
            along = sims4.math.vector_normalize(stop - start)
            inward = sims4.math.vector3_rotate_axis_angle(along, sims4.math.PI/2, sims4.math.Vector3.Y_AXIS())
            if inward_dir:
                polygon = sims4.geometry.Polygon([start, start + constraint_width*inward, stop + constraint_width*inward, stop])
            else:
                polygon = sims4.geometry.Polygon([start, stop, stop - constraint_width*inward, start - constraint_width*inward])
            polygons.append(polygon)
        routing_surface = self.routing_surface
        if return_constraint_list:
            constraint_list = []
            for polygon in polygons:
                restricted_polygon = sims4.geometry.RestrictedPolygon(polygon, ())
                constraint = Constraint(routing_surface=routing_surface, geometry=restricted_polygon, los_reference_point=los_reference_point, posture_state_spec=STAND_AT_NONE_POSTURE_STATE_SPEC)
                constraint_list.append(constraint)
            return constraint_list
        else:
            geometry = sims4.geometry.RestrictedPolygon(sims4.geometry.CompoundPolygon(polygons), ())
            constraint = Constraint(routing_surface=routing_surface, geometry=geometry, posture_state_spec=STAND_AT_NONE_POSTURE_STATE_SPEC)
            return constraint

    def get_created_constraint(self, tuned_constraint):
        if not self.additional_interaction_constraints:
            return
        if not self._created_constraints:
            self._created_constraints = {}
        if self._created_constraints_dirty:
            self._created_constraints.clear()
            for tuned_additional_constraint in self.additional_interaction_constraints:
                constraint = tuned_additional_constraint.constraint
                if constraint is not None:
                    self._created_constraints[constraint] = constraint.create_constraint(None, self)
            self._created_constraints_dirty = False
        return self._created_constraints.get(tuned_constraint)

    @forward_to_components
    def register_rebate_tests(self, test_set):
        pass

    @forward_to_components
    def validate_definition(self):
        pass

    def _should_invalidate_location(self):
        parent = self.parent
        if parent is None:
            return True
        return parent._should_invalidate_location()

    def _notify_buildbuy_of_location_change(self, old_location):
        if self.persistence_group == PersistenceGroups.OBJECT and self._should_invalidate_location():
            invalidate_object_location(self.id)

    def set_build_buy_lockout_state(self, lockout_state, lockout_timer=None):
        if self._build_buy_lockout_alarm_handler is not None:
            alarms.cancel_alarm(self._build_buy_lockout_alarm_handler)
            self._build_buy_lockout_alarm_handler = None
        elif self._build_buy_lockout and lockout_state:
            return
        if lockout_state:
            if lockout_timer is not None:
                time_span_real_time = clock.interval_in_real_seconds(lockout_timer)
                self._build_buy_lockout_alarm_handler = alarms.add_alarm_real_time(self, time_span_real_time, lambda *_: self.set_build_buy_lockout_state(False))
        if lockout_state and not self.build_buy_lockout:
            self.reset(ResetReason.RESET_EXPECTED)
        self._build_buy_lockout = lockout_state
        self.resend_interactable()
        self.resend_tint()

    def on_location_changed(self, old_location):
        super().on_location_changed(old_location)
        self.mark_get_locations_for_posture_needs_update()
        self.clear_check_line_of_sight_cache()
        self._provided_surface = UNSET
        if self.id:
            self._update_persistence_group()
            self._notify_buildbuy_of_location_change(old_location)
            self.manager.on_location_changed(self)
            if self._on_location_changed_callbacks is not None:
                self._on_location_changed_callbacks(self, old_location, self.location)
            self._created_constraints_dirty = True

    def set_object_def_state_index(self, state_index):
        if type(self) != self.get_class_for_obj_state(state_index):
            logger.error("Attempting to change object {}'s state to one that would require a different runtime class.  This is not supported.", self, owner='tastle')
        self.apply_definition(self.definition, state_index)
        self.model = self._model
        self.rig = self._rig
        self.resend_state_index()
        self.resend_slot()

    def register_on_location_changed(self, callback):
        if self._on_location_changed_callbacks is None:
            self._on_location_changed_callbacks = CallableListConsumingExceptions()
        self._on_location_changed_callbacks.append(callback)

    def unregister_on_location_changed(self, callback):
        if self._on_location_changed_callbacks is None:
            logger.error('Unregistering location changed callback on {} when there are none registered.', self)
            return
        if callback not in self._on_location_changed_callbacks:
            logger.error('Unregistering location changed callback on {} that is not registered. Callback: {}.', self, callback)
            return
        self._on_location_changed_callbacks.remove(callback)
        if not self._on_location_changed_callbacks:
            self._on_location_changed_callbacks = None

    def is_on_location_changed_callback_registered(self, callback):
        return callback in self._on_location_changed_callbacks

    def register_on_hidden_or_shown(self, callback):
        if self._on_hidden_or_shown_callbacks is None:
            self._on_hidden_or_shown_callbacks = CallableListConsumingExceptions()
        self._on_hidden_or_shown_callbacks.append(callback)

    def unregister_on_hidden_or_shown(self, callback):
        if self._on_hidden_or_shown_callbacks is None:
            logger.error('Unregistering hidden or shown callback on {} when there are none registered.', self)
            return
        if callback not in self._on_hidden_or_shown_callbacks:
            logger.error('Unregistering hidden or shown callback on {} that is not registered. Callback: {}.', self, callback)
            return
        self._on_hidden_or_shown_callbacks.remove(callback)
        if not self._on_hidden_or_shown_callbacks:
            self._on_hidden_or_shown_callbacks = None

    def is_on_hidden_or_shown_callback_registered(self, callback):
        if self._on_hidden_or_shown_callbacks is None:
            return False
        return callback in self._on_hidden_or_shown_callbacks

    def is_on_active_lot(self, tolerance=0):
        return self.persistence_group == PersistenceGroups.OBJECT

    @property
    def is_in_navmesh(self):
        if self._routing_context is not None and self._routing_context.object_footprint_id is not None:
            return True
        else:
            return False

    @property
    def may_move(self):
        return self.vehicle_component is not None or self.routing_component is not None and self.routing_component.object_routing_component is not None

    def get_surface_override_for_posture(self, source_posture_name):
        pass

    @property
    def add_to_posture_graph_if_parented(self):
        return self._add_to_posture_graph_if_parented

    @classproperty
    def provided_mobile_posture_affordances(cls):
        return cls._provided_mobile_posture_affordances or EMPTY_SET

    def get_joint_transform_for_joint(self, joint_name):
        transform = get_joint_transform_from_rig(self.rig, joint_name)
        transform = Transform.concatenate(transform, self.transform)
        return transform

    @property
    def object_radius(self):
        if self._routing_context is None:
            return routing.get_default_agent_radius()
        return self._routing_context.object_radius

    @property
    def persistence_group(self):
        return self._persistence_group

    @persistence_group.setter
    def persistence_group(self, value):
        self._persistence_group = value

    def _update_persistence_group(self):
        if self.is_in_inventory():
            self.persistence_group = objects.persistence_groups.PersistenceGroups.OBJECT
            return
        if self.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT:
            if not services.current_zone().lot.is_position_on_lot(self.position, 0):
                remove_object_from_buildbuy_system(self.id)
                self.persistence_group = objects.persistence_groups.PersistenceGroups.IN_OPEN_STREET
        elif self.persistence_group == objects.persistence_groups.PersistenceGroups.IN_OPEN_STREET and services.current_zone().lot.is_position_on_lot(self.position, 0):
            self.persistence_group = objects.persistence_groups.PersistenceGroups.OBJECT
            add_object_to_buildbuy_system(self.id)

    def _fixup_pool_surface(self):
        if (self.item_location == ItemLocation.FROM_WORLD_FILE or self.item_location == ItemLocation.FROM_CONDITIONAL_LAYER) and (self.routing_surface.type != SurfaceType.SURFACETYPE_POOL and build_buy.PlacementFlags.REQUIRES_WATER_SURFACE & build_buy.get_object_placement_flags(self.definition.id)) and get_water_depth_at_location(self.location) > 0:
            routing_surface = self.routing_surface
            self.set_location(self.location.clone(routing_surface=SurfaceIdentifier(routing_surface.primary_id, routing_surface.secondary_id, SurfaceType.SURFACETYPE_POOL)))

    def _add_to_world(self):
        if self.persistence_group == PersistenceGroups.OBJECT:
            add_object_to_buildbuy_system(self.id)

    def _remove_from_world(self):
        if self.persistence_group == PersistenceGroups.OBJECT:
            remove_object_from_buildbuy_system(self.id)

    def on_add(self):
        super().on_add()
        self._add_to_world()
        self.register_on_location_changed(self._location_changed)
        if self.is_fire_related_object:
            fire_service = services.get_fire_service()
            self.register_on_location_changed(fire_service.flammable_object_location_changed)
        posture_graph_service = services.posture_graph_service()
        if posture_graph.is_object_mobile_posture_compatible(self):
            self.register_on_location_changed(posture_graph_service.mobile_posture_object_location_changed)
        if self.provided_mobile_posture_affordances:
            posture_graph_service.add_mobile_posture_provider(self)
        services.call_to_action_service().object_created(self)
        self.try_mark_as_new_object()

    def on_remove(self):
        zone = services.current_zone()
        if zone is not None and not zone.is_zone_shutting_down:
            services.get_event_manager().process_event(test_events.TestEvent.ObjectDestroyed, obj=self)
        super().on_remove()
        if not zone.is_zone_shutting_down:
            self._remove_from_world()
            self.unregister_on_location_changed(self._location_changed)
            if self.is_fire_related_object:
                fire_service = services.get_fire_service()
                if fire_service is not None:
                    self.unregister_on_location_changed(fire_service.flammable_object_location_changed)
            posture_graph_service = services.posture_graph_service()
            if self.provided_mobile_posture_affordances:
                posture_graph_service.remove_mobile_posture_provider(self)
            if posture_graph.is_object_mobile_posture_compatible(self):
                posture_graph_service.remove_object_from_mobile_posture_quadtree(self)
                self.unregister_on_location_changed(posture_graph_service.mobile_posture_object_location_changed)
        else:
            self._on_location_changed_callbacks = None

    def on_added_to_inventory(self):
        super().on_added_to_inventory()
        self._remove_from_world()
        self.visibility = VisibilityState(False)

    def on_removed_from_inventory(self):
        super().on_removed_from_inventory()
        self._add_to_world()
        self.visibility = VisibilityState(True)

    @forward_to_components
    def on_buildbuy_exit(self):
        self._update_location_callbacks(update_surface=True)

    def _update_location_callbacks(self, update_surface=False):
        self._inside_status_change()
        self._natural_ground_status_change()
        if update_surface:
            self._surface_type_changed()

    @staticmethod
    def _location_changed(obj, old_loc, new_loc):
        if obj.zone_id:
            obj._update_location_callbacks(update_surface=old_loc.routing_surface != new_loc.routing_surface)
        obj._fixup_pool_surface()

    @forward_to_components
    def _surface_type_changed(self):
        pass

    def _inside_status_change(self, *_, **__):
        if self.is_outside:
            self._set_placed_outside()
        else:
            self._set_placed_inside()

    def _natural_ground_status_change(self, *_, **__):
        if self.routing_surface is not None and self.routing_surface.type == SurfaceType.SURFACETYPE_POOL:
            return
        if self.is_on_natural_ground():
            self._set_placed_on_natural_ground()
        else:
            self._set_placed_off_natural_ground()

    @forward_to_components
    def _set_placed_outside(self):
        pass

    @forward_to_components
    def _set_placed_inside(self):
        pass

    @forward_to_components
    def _set_placed_on_natural_ground(self):
        pass

    @forward_to_components
    def _set_placed_off_natural_ground(self):
        pass

    @ored_forward_to_components
    def on_hovertip_requested(self):
        return False

    @property
    def is_outside(self):
        routing_surface = self.routing_surface
        level = 0 if routing_surface is None else routing_surface.secondary_id
        try:
            return is_location_outside(self.position, level)
        except RuntimeError:
            pass

    def is_on_natural_ground(self):
        if self.parent is not None:
            return False
        routing_surface = self.routing_surface
        level = 0 if routing_surface is None else routing_surface.secondary_id
        try:
            return is_location_natural_ground(self.position, level)
        except RuntimeError:
            pass

    def try_mark_as_new_object(self):
        if not (self.should_mark_as_new and services.current_zone().is_in_build_buy):
            return
        self.add_dynamic_component(objects.components.types.NEW_OBJECT_COMPONENT)

    def on_child_added(self, child, location):
        super().on_child_added(child, location)
        self.get_raycast_root().on_leaf_child_changed()

    def on_child_removed(self, child, new_parent=None):
        super().on_child_removed(child, new_parent=new_parent)
        self.get_raycast_root().on_leaf_child_changed()

    def on_leaf_child_changed(self):
        if self._raycast_context is not None:
            self._create_raycast_context()

    @property
    def forward_direction_for_picking(self):
        return sims4.math.Vector3.Z_AXIS()

    @property
    def route_target(self):
        parts = self.parts
        if parts is None:
            return (RouteTargetType.OBJECT, self)
        else:
            return (RouteTargetType.PARTS, parts)

    @property
    def should_mark_as_new(self):
        return True

    def is_surface(self, include_parts=False, ignore_deco_slots=False):
        if self._is_surface is None:
            self._is_surface = {}
        key = (include_parts, ignore_deco_slots)
        is_surface = self._is_surface.get(key)
        if is_surface is not None:
            return is_surface
        inventory_component = self.inventory_component
        if inventory_component is not None and inventory_component.has_get_put:
            self._is_surface[key] = True
            return True

        def is_valid_surface_slot(slot_type):
            if not (ignore_deco_slots and slot_type.is_deco_slot) and slot_type.is_surface:
                return True
            return False

        for runtime_slot in self.get_runtime_slots_gen():
            if not include_parts and runtime_slot.owner is not self:
                continue
            if not any(is_valid_surface_slot(slot_type) for slot_type in runtime_slot.slot_types):
                continue
            if not runtime_slot.owner.is_same_object_or_part(self):
                continue
            self._is_surface[key] = True
            return True
        else:
            self._is_surface[key] = False
            return False

    def get_save_lot_coords_and_level(self):
        lot_coord_msg = LotCoord()
        parent = self.parent
        if parent is not None and parent.is_sim:
            parent.force_update_routing_location()
            starting_position = parent.position + parent.forward
            starting_location = placement.create_starting_location(position=starting_position, orientation=parent.orientation, routing_surface=self.location.world_routing_surface)
            fgl_context = placement.create_fgl_context_for_object(starting_location, self)
            (trans, orient) = placement.find_good_location(fgl_context)
            if trans is None:
                logger.warn('Unable to find good location to save object{}, which is parented to sim {} and cannot go into an inventory. Defaulting to location of sim.', self, parent)
                transform = parent.transform
            else:
                transform = sims4.math.Transform(trans, orient)
            if self.persistence_group == PersistenceGroups.OBJECT:
                transform = services.current_zone().lot.convert_to_lot_coordinates(transform)
        elif self.persistence_group == PersistenceGroups.OBJECT:
            transform = services.current_zone().lot.convert_to_lot_coordinates(self.transform)
        else:
            transform = self.transform
        lot_coord_msg.x = transform.translation.x
        lot_coord_msg.y = transform.translation.y
        lot_coord_msg.z = transform.translation.z
        lot_coord_msg.rot_x = transform.orientation.x
        lot_coord_msg.rot_y = transform.orientation.y
        lot_coord_msg.rot_z = transform.orientation.z
        lot_coord_msg.rot_w = transform.orientation.w
        if self.location.world_routing_surface is not None:
            level = self.location.level
        else:
            level = 0
        return (lot_coord_msg, level)

    def save_object(self, object_list, *args, **kwargs):
        save_data = super().save_object(object_list, *args, **kwargs)
        if save_data is None:
            return
        save_data.slot_id = self.bone_name_hash
        (save_data.position, save_data.level) = self.get_save_lot_coords_and_level()
        inventory_plex_id = self.get_inventory_plex_id()
        if inventory_plex_id is not None:
            save_data.inventory_plex_id = inventory_plex_id
        save_data.scale = self.scale
        save_data.state_index = self.state_index
        if hasattr(save_data, 'buildbuy_use_flags'):
            save_data.buildbuy_use_flags = self._build_buy_use_flags
        save_data.cost = self.base_value
        save_data.ui_metadata = self.ui_metadata._value
        self.post_tooltip_save_data_stored()
        save_data.is_new = self.new_in_inventory
        save_data.is_new_object = self.is_new_object
        self.populate_icon_canvas_texture_info(save_data)
        if self._household_owner_id is not None:
            save_data.owner_id = self._household_owner_id
        save_data.needs_depreciation = self._needs_depreciation
        save_data.needs_post_bb_fixup = self._needs_post_bb_fixup
        if self._persisted_tags:
            save_data.persisted_tags.extend(self._persisted_tags)
        if self._multicolor is not None:
            for color in self._multicolor:
                color = getattr(color, 'value', color)
                multicolor_info_msg = save_data.multicolor.add()
                (multicolor_info_msg.x, multicolor_info_msg.y, multicolor_info_msg.z, _) = sims4.color.to_rgba(color)
        save_data.created_from_lot_template = False
        save_data.stack_sort_order = self.get_stack_sort_order()
        if self.material_state:
            save_data.material_state = self.material_state.state_name_hash
        if self.geometry_state:
            save_data.geometry_state = self.geometry_state
        if self.model:
            model_key = sims4.resources.get_protobuff_for_key(self.model)
            save_data.model_override_resource_key = model_key
        parent = self.bb_parent
        if parent is not None:
            if not parent.is_sim:
                save_data.parent_id = parent.id
        if not (parent is None or not parent.is_sim):
            save_data.object_parent_type = self._parent_type
            save_data.encoded_parent_location = self._parent_location
        inventory = self.inventory_component
        if inventory is not None:
            if not inventory.is_shared_inventory:
                save_data.unique_inventory = inventory.save_items()
        return save_data

    def load_object(self, object_data, **kwargs):
        if object_data.HasField('owner_id'):
            self._household_owner_id = object_data.owner_id
        if self.is_downloaded:
            self.base_value = self.catalog_value
        else:
            self.base_value = object_data.cost
        self.new_in_inventory = object_data.is_new
        super().load_object(object_data, **kwargs)
        if object_data.HasField('texture_id') and self.canvas_component is not None:
            self.canvas_component.set_painting_texture_id(object_data.texture_id)
        if object_data.HasField('needs_depreciation'):
            self._needs_depreciation = object_data.needs_depreciation
        if object_data.HasField('needs_post_bb_fixup'):
            self._needs_post_bb_fixup = object_data.needs_post_bb_fixup
        else:
            self._needs_post_bb_fixup = self._needs_depreciation
        inventory = self.inventory_component
        if inventory is not None:
            inventory.load_items(object_data.unique_inventory)
        if sims4.protocol_buffer_utils.has_field(object_data, 'buildbuy_use_flags'):
            self._build_buy_use_flags = object_data.buildbuy_use_flags
        self.is_new_object = object_data.is_new_object
        if self.is_new_object:
            self.add_dynamic_component(objects.components.types.NEW_OBJECT_COMPONENT)
        if object_data.persisted_tags is not None:
            self.append_tags(set(object_data.persisted_tags))

    def finalize(self, **kwargs):
        super().finalize(**kwargs)
        self.try_post_bb_fixup(**kwargs)
        if self.is_fire_related_object:
            fire_service = services.get_fire_service()
            if fire_service is not None:
                fire_service.flammable_object_location_changed(self)
        if posture_graph.is_object_mobile_posture_compatible(self):
            posture_graph_service = services.current_zone().posture_graph_service
            posture_graph_service.mobile_posture_object_location_changed(self)

    def set_household_owner_id(self, new_owner_id):
        self._household_owner_id = new_owner_id
        self._resend_household_owner_id()
        if self.live_drag_component is not None:
            self.live_drag_component.resolve_live_drag_household_permission()

    def get_household_owner_id(self):
        return self._household_owner_id

    def get_object_property(self, property_type):
        if property_type == GameObjectProperty.CATALOG_PRICE:
            return self.definition.price
        if property_type == GameObjectProperty.MODIFIED_PRICE:
            return self.current_value
        if property_type == GameObjectProperty.RARITY:
            return self.get_object_rarity_string()
        if property_type == GameObjectProperty.GENRE:
            return Genre.get_genre_localized_string(self)
        if property_type == GameObjectProperty.RECIPE_NAME or property_type == GameObjectProperty.RECIPE_DESCRIPTION:
            return self.get_craftable_property(self, property_type)
        if property_type == GameObjectProperty.OBJ_TYPE_REL_ID:
            return services.relationship_service().get_object_type_rel_id(self)
        logger.error('Requested property_type {} not found on game_object'.format(property_type), owner='camilogarcia')

    def update_ownership(self, sim, make_sim_owner=True):
        household_id = sim.household_id
        if self._household_owner_id != household_id:
            if self.ownable_component is not None:
                self.ownable_component.update_sim_ownership(None)
            self.set_household_owner_id(household_id)
        if make_sim_owner and self.ownable_component is not None:
            self.ownable_component.update_sim_ownership(sim.sim_id)

    @property
    def flammable(self):
        fire_service = services.get_fire_service()
        if fire_service is not None:
            return fire_service.is_object_flammable(self)
        return False

    def object_bounds_for_flammable_object(self, fire_retardant_bonus):
        return self.flammable_area.get_bounds_for_flammable_object(self, fire_retardant_bonus)

    @property
    def is_set_as_head(self):
        parent = self.parent
        if parent is None:
            return False
        if not parent.is_sim:
            return False
        if parent.current_object_set_as_head is None:
            return False
        else:
            parent_head = parent.current_object_set_as_head()
            if not self.is_same_object_or_part(parent_head):
                return False
        return True

    @classmethod
    def register_tuned_animation(cls, *_, **__):
        pass

    @classmethod
    def add_auto_constraint(cls, *_, **__):
        pass

    def may_reserve(self, sim, *args, **kwargs):
        for child in self.children:
            child_targets = child.parts if child.parts else (child,)
            for child_target in child_targets:
                if child_target.is_sim:
                    continue
                reserve_result = child_target.may_reserve(sim, *args, **kwargs)
                if not reserve_result:
                    return reserve_result
        return super().may_reserve(sim, *args, **kwargs)

    def make_transient(self):
        self.transient = True
        self._destroy_if_not_in_use()

    def _destroy_if_not_in_use(self):
        if self.is_part:
            self.part_owner._destroy_if_not_in_use()
            return
        if self.self_or_part_in_use:
            return
        if not self.transient:
            return
        self.schedule_destroy_asap(source=self, cause='Destroying unused transient object.')
        posture_graph_service = services.current_zone().posture_graph_service
        if posture_graph_service.is_object_pending_deletion(self):
            posture_graph_service.finalize_object_deletion(self)

    def remove_reservation_handler(self, *args, **kwargs):
        super().remove_reservation_handler(*args, **kwargs)
        self._destroy_if_not_in_use()

    def schedule_element(self, timeline, element):
        resettable_element = reset.ResettableElement(element, self)
        resettable_element.on_scheduled(timeline)
        timeline.schedule(resettable_element)
        return resettable_element

    def register_reset_element(self, element):
        if self._scheduled_elements is None:
            self._scheduled_elements = set()
        self._scheduled_elements.add(element)

    def unregister_reset_element(self, element):
        if self._scheduled_elements is not None:
            self._scheduled_elements.discard(element)
            if not self._scheduled_elements:
                self._scheduled_elements = None

    def on_reset_element_hard_stop(self):
        self.reset(reset_reason=ResetReason.RESET_EXPECTED)

    def on_reset_get_elements_to_hard_stop(self, reset_reason):
        elements_to_reset = super().on_reset_get_elements_to_hard_stop(reset_reason)
        if self._scheduled_elements is not None:
            scheduled_elements = list(self._scheduled_elements)
            self._scheduled_elements = None
            for element in scheduled_elements:
                elements_to_reset.append(element)
                element.unregister()
        return elements_to_reset

    def get_gsi_portal_items(self, key_name, value_name):
        household_owner_id = self.household_owner_id
        household_owner = services.household_manager().get(household_owner_id)
        name = household_owner.name if household_owner is not None else 'Not Owned'
        return [{key_name: 'Household Owner', value_name: name}]
Esempio n. 13
0
class TutorialService(Service):
    INTERACTION_DISABLED_TOOLTIP = sims4.localization.TunableLocalizedStringFactory(description='\n        Default Tooltip for disabled interactions.\n        ')
    TUTORIAL_DRAMA_NODE = TunableReference(description='\n        The drama node that controls the tutorial.\n        ', manager=services.get_instance_manager(sims4.resources.Types.DRAMA_NODE), class_restrictions=('TutorialDramaNode',))
    FALLBACK_RESTRICTED_AFFORDANCES = OptionalTunable(description='\n        If enabled, use this affordance restriction if we are in the tutorial \n        mode and somehow no restriction has currently been specified by a \n        tutorial tip.  (There should always be a restriction.)\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                ')))

    def __init__(self):
        self._visible_affordance_filter = None
        self._enabled_affordance_filter = None
        self._tooltip = None
        self._tutorial_alarms = {}
        self._unselectable_sim_id = None
        self._unselectable_sim_count = 0
        self._tutorial_mode = TutorialMode.STANDARD

    def add_tutorial_alarm(self, tip, callback, time_of_day):
        now = services.game_clock_service().now()
        time_till_satisfy = now.time_till_next_day_time(time_of_day)
        self._tutorial_alarms[tip] = add_alarm(self, time_till_satisfy, callback, cross_zone=True)

    def remove_tutorial_alarm(self, tip):
        del self._tutorial_alarms[tip]

    def is_affordance_visible(self, affordance):
        visible_filter = self._visible_affordance_filter
        if visible_filter is None:
            if self._tutorial_mode == TutorialMode.FTUE:
                if self.FALLBACK_RESTRICTED_AFFORDANCES:
                    visible_filter = self.FALLBACK_RESTRICTED_AFFORDANCES.visible_affordances
        return visible_filter is None or visible_filter(affordance)

    def get_disabled_affordance_tooltip(self, affordance):
        enabled_filter = self._enabled_affordance_filter
        disabled_text = self._tooltip
        if enabled_filter is None and self._tutorial_mode == TutorialMode.FTUE:
            enabled_filter = self.FALLBACK_RESTRICTED_AFFORDANCES.enabled_affordances
            disabled_text = self.FALLBACK_RESTRICTED_AFFORDANCES.tooltip
            return
        if enabled_filter is None or enabled_filter(affordance):
            return
        if disabled_text is not None:
            return disabled_text
        return self.INTERACTION_DISABLED_TOOLTIP

    def clear_restricted_affordances(self):
        self._visible_affordance_filter = None
        self._enabled_affordance_filter = None
        self._tooltip = None

    def set_restricted_affordances(self, visible_filter, tooltip, enabled_filter):
        self._visible_affordance_filter = visible_filter
        self._enabled_affordance_filter = enabled_filter
        self._tooltip = tooltip

    def on_all_households_and_sim_infos_loaded(self, client):
        save_slot_data_msg = services.get_persistence_service().get_save_slot_proto_buff()
        if save_slot_data_msg.trigger_tutorial_drama_node:
            save_slot_data_msg.trigger_tutorial_drama_node = False
            drama_scheduler = services.drama_scheduler_service()
            if drama_scheduler is not None:
                resolver = SingleSimResolver(services.active_sim_info())
                drama_scheduler.run_node(self.TUTORIAL_DRAMA_NODE, resolver)
        self._tutorial_mode = save_slot_data_msg.tutorial_mode

    def set_tutorial_mode(self, mode):
        save_slot_data_msg = services.get_persistence_service().get_save_slot_proto_buff()
        save_slot_data_msg.tutorial_mode = mode
        self._tutorial_mode = mode
        if mode != TutorialMode.FTUE:
            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_nodes[0].end()

    def is_sim_unselectable(self, sim_info):
        return sim_info.sim_id == self._unselectable_sim_id

    def set_unselectable_sim(self, sim_info):
        if sim_info is None:
            sim_id = None
        else:
            sim_id = sim_info.sim_id
        if sim_id != self._unselectable_sim_id:
            if sim_id is None:
                self._unselectable_sim_count -= 1
                if self._unselectable_sim_count > 0:
                    return
            elif self._unselectable_sim_id is None:
                self._unselectable_sim_count = 1
            else:
                logger.error('Tutorial only supports one unselectable sim at a time.  Attempting to add:{}', sim_info)
                return
            self._unselectable_sim_id = sim_id
            client = services.client_manager().get_first_client()
            if client is not None:
                client.selectable_sims.notify_dirty()
                if sim_id is not None:
                    client.validate_selectable_sim()
        elif sim_id is not None:
            self._unselectable_sim_count += 1

    def is_tutorial_running(self):
        drama_scheduler = services.drama_scheduler_service()
        if drama_scheduler is None or not drama_scheduler.get_running_nodes_by_drama_node_type(DramaNodeType.TUTORIAL):
            return False
        return True
Esempio n. 14
0
 def __init__(
         self,
         description='Holds information about carrying and putting down an object.',
         **kwargs):
     super().__init__(
         put_down_tuning=TunableVariant(reference=TunableReference(
             description=
             '\n                    Tuning for how to score where a Sim might want to set an\n                    object down.\n                    ',
             manager=services.get_instance_manager(
                 sims4.resources.Types.STRATEGY)),
                                        literal=TunablePutDownStrategy().
                                        TunableFactory(),
                                        default='literal'),
         state_based_put_down_tuning=TunableMapping(
             description=
             '\n                A mapping from a state value to a putdownstrategy. If the\n                owning object is in any of the states tuned here, it will use\n                that state\'s associated putdownstrategy in place of the one\n                putdownstrategy tuned in the "put_down_tuning" field. If the\n                object is in multiple states listed in this mapping, the\n                behavior is undefined.\n                ',
             key_type=TunableReference(
                 description=
                 '\n                    The state value this object must be in in order to use the\n                    associated putdownstrategy.\n                    ',
                 manager=services.get_instance_manager(
                     sims4.resources.Types.OBJECT_STATE)),
             value_type=TunableVariant(reference=TunableReference(
                 description=
                 '\n                        Tuning for how to score where a Sim might want to set\n                        an object down.\n                        ',
                 manager=services.get_instance_manager(
                     sims4.resources.Types.STRATEGY)),
                                       literal=TunablePutDownStrategy().
                                       TunableFactory()),
             key_name='State',
             value_name='PutDownStrategy'),
         carry_affordances=OptionalTunable(TunableList(
             TunableReference(
                 description=
                 '\n                    The versions of the HoldObject affordance that this object\n                    supports.\n                    ',
                 manager=services.affordance_manager())),
                                           disabled_name=
                                           'use_default_affordances',
                                           enabled_name=
                                           'use_custom_affordances'),
         provided_affordances=TunableList(
             description=
             '\n                A list of affordances that are generated when a Sim holding\n                this object selects another Sim to interact with. The generated\n                interactions will target the selected Sim but will have this\n                object set as their carry target.\n                ',
             tunable=TunableReference(
                 manager=services.affordance_manager())),
         constraint_pick_up=OptionalTunable(
             description=
             '\n                A list of constraints that must be fulfilled in order to\n                interact with this object.\n                ',
             tunable=TunableList(
                 tunable=interactions.constraints.TunableConstraintVariant(
                     description=
                     '\n                        A constraint that must be fulfilled in order to\n                        interact with this object.\n                        '
                 ))),
         allowed_hands=TunableVariant(locked_args={
             'both': (Hand.LEFT, Hand.RIGHT),
             'left_only': (Hand.LEFT, ),
             'right_only': (Hand.RIGHT, )
         },
                                      default='both'),
         holster_while_routing=Tunable(
             description=
             '\n                If True, the Sim will holster the object before routing and\n                unholster when the route is complete.\n                ',
             tunable_type=bool,
             default=False),
         holster_compatibility=TunableAffordanceFilterSnippet(
             description=
             '\n                Define interactions for which holstering this object is\n                explicitly disallowed.\n                \n                e.g. The Scythe is tuned to be holster-incompatible with\n                sitting, meaning that Sims will holster the Sctyhe when sitting.\n                '
         ),
         unholster_on_long_route_only=Tunable(
             description=
             '\n                If True, then the Sim will not unholster this object (assuming\n                it was previously holstered) unless a transition involving a\n                long route is about to happen.\n                \n                If False, then the standard holstering rules apply.\n                ',
             tunable_type=bool,
             default=False),
         prefer_owning_sim_inventory_when_not_on_home_lot=Tunable(
             description=
             "\n                If checked, this object will highly prefer to be put into the\n                owning Sim's inventory when being put down by the owning Sim on\n                a lot other than their home lot.\n                \n                Certain objects, like consumables, should be exempt from this.\n                ",
             tunable_type=bool,
             default=True),
         description=description,
         **kwargs)
Esempio n. 15
0
 def __init__(self, **kwargs):
     super().__init__(threshold_value=Tunable(int, -80, description='Threshold for which below the sim is in commodity distress'), buff=TunableBuffReference(description='Buff that gets added to the sim when they are in the commodity distress state.'), distress_interaction=TunableReference(services.get_instance_manager(sims4.resources.Types.INTERACTION), description='The interaction to be pushed on the sim when the commodity reaches distress.'), incompatible_interactions=TunableAffordanceFilterSnippet(), replacement_affordance=TunableReference(services.get_instance_manager(sims4.resources.Types.INTERACTION), description='The affordance that will be pushed when the commodity '), priority=Tunable(int, 0, description='The relative priority of the override interaction being played over others.'), description='The behaviors that show that the commodity is in distress.', **kwargs)