def __init__(self, **kwargs):
     super().__init__(participant_type=TunableEnumFlags(
         description=
         '\n                The Participant who owns the lifestyle brand we want to use.\n                ',
         enum_type=ParticipantTypeSingle,
         default=ParticipantType.Actor),
                      **kwargs)
class RelationshipBitCountTest(HasTunableSingletonFactory, AutoFactoryInit,
                               BaseTest):
    FACTORY_TUNABLES = {
        'subject':
        TunableEnumFlags(
            description=
            '\n            Owner of the relationship.\n            ',
            enum_type=ParticipantTypeSingleSim,
            default=ParticipantTypeSingleSim.Actor),
        'rel_bit':
        TunablePackSafeReference(
            description=
            "\n            The type of relationship we're looking for.\n            \n            In other words, we're looking for any relationship\n            with this Rel Bit.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.RELATIONSHIP_BIT)),
        'relationship_count':
        TunableRange(
            description=
            "\n            The number of relationships we want to compare against\n            the sim's actual number of relationships.\n            ",
            tunable_type=int,
            minimum=0,
            default=0),
        'comparison_operator':
        TunableOperator(
            description=
            "\n            The operator to use to compare the sim's\n            actual relationship count vs. the tuned\n            Relationship Count.\n            ",
            default=Operator.EQUAL)
    }

    def get_expected_args(self):
        return {'sim_infos': self.subject}

    @cached_test
    def __call__(self, sim_infos):
        if self.rel_bit is None:
            return TestResult(
                False,
                'Failed relationship bit count test: Rel Bit is not available due to pack-safeness'
            )
        sim_info_manager = services.sim_info_manager()
        for sim_info in sim_infos:
            rel_tracker = sim_info.relationship_tracker
            actual_rel_count = 0
            for other_sim_info_id in sim_info.relationship_tracker.target_sim_gen(
            ):
                other_sim_info = sim_info_manager.get(other_sim_info_id)
                if other_sim_info is None:
                    continue
                if rel_tracker.has_bit(other_sim_info_id, self.rel_bit):
                    actual_rel_count += 1
            threshold = sims4.math.Threshold(self.relationship_count,
                                             self.comparison_operator)
            if not threshold.compare(actual_rel_count):
                operator_symbol = Operator.from_function(
                    self.comparison_operator).symbol
                return TestResult(
                    False,
                    'Failed relationship bit count test: Actual Relationship Count ({}) {} Tuned Relationship Count ({})',
                    actual_rel_count, operator_symbol, self.relationship_count)
        return TestResult.TRUE
Exemple #3
0
class LeaveSituationElement(XevtTriggeredElement):
    FACTORY_TUNABLES = {
        'situation_types':
        TunableList(
            description=
            '\n            A list of all situations the Sim needs to leave.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION),
                                     pack_safe=True)),
        'subject':
        TunableEnumFlags(
            description=
            '\n            The participant of who will join the situation.\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.Actor)
    }

    def _do_behavior(self, *args, **kwargs):
        subject = self.interaction.get_participant(self.subject)
        if subject is None or not subject.is_sim:
            logger.error(
                'Fail to leave situation since subject {} is not sim for interaction {}',
                self.subject,
                self.interaction,
                owner='cjiang')
            return False
        situation_manager = services.get_zone_situation_manager()
        for situation in situation_manager.running_situations():
            if isinstance(situation, self.situation_types):
                situation_manager.remove_sim_from_situation(
                    subject, situation.id)
        return True
Exemple #4
0
class PaintingTexture(metaclass=TunedInstanceMetaclass,
                      manager=services.get_instance_manager(
                          sims4.resources.Types.RECIPE)):
    __qualname__ = 'PaintingTexture'
    INSTANCE_TUNABLES = {
        'texture':
        TunableResourceKey(None, resource_types=[sims4.resources.Types.TGA]),
        'tests':
        TunableTestSet(),
        'canvas_types':
        TunableEnumFlags(
            CanvasType,
            CanvasType.NONE,
            description=
            '\n            The canvas types (generally, aspect ratios) with which this texture\n            may be used.\n            '
        )
    }

    @classmethod
    def _tuning_loaded_callback(cls):
        if cls.texture:
            cls._base_painting_state = PaintingState.from_key(cls.texture)
        else:
            cls._base_painting_state = None

    @classmethod
    def apply_to_object(cls, obj):
        obj.canvas_component.painting_state = cls._base_painting_state
Exemple #5
0
class DestroySituationsByTagsMixin:
    FACTORY_TUNABLES = {
        'situation_tags':
        TunableSet(
            description=
            '\n            A situation must match at least one of the tuned tags in order to\n            be destroyed.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=Tag,
                                          filter_prefixes=['situation'],
                                          default=Tag.INVALID,
                                          pack_safe=True)),
        'required_participant':
        TunableEnumFlags(
            description=
            '\n            If tuned, only situations with this participant will be destroyed.\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.Invalid)
    }

    def _destroy_situations_by_tags(self, resolver):
        situation_manager = services.get_zone_situation_manager()
        situations = situation_manager.get_situations_by_tags(
            self.situation_tags)
        if situations:
            participant = None
            if self.required_participant is not None and self.required_participant != ParticipantType.Invalid:
                participant = resolver.get_participant(
                    self.required_participant)
                if participant is None or not participant.is_sim:
                    return False
            for situation in situations:
                if participant and not situation.is_sim_info_in_situation(
                        participant.sim_info):
                    continue
                situation_manager.destroy_situation_by_id(situation.id)
        return True
class _BroadcasterEffectTested(_BroadcasterEffect):
    FACTORY_TUNABLES = {
        'tests':
        TunableTestSet(
            description=
            '\n            Tests that must pass in order for the broadcaster effect to be\n            applied.\n            '
        ),
        'excluded_participants':
        OptionalTunable(
            description=
            '\n            If enabled, these participants will be excluded from this broadcaster effect.\n            ',
            tunable=TunableEnumFlags(
                description=
                '\n                A set of Participants that will be excluded from having this effect\n                applied to them. If the broadcaster comes from an interaction,\n                these participants will come from that interaction.\n                ',
                enum_type=ParticipantType,
                default=ParticipantType.Actor | ParticipantType.TargetSim))
    }

    def _should_apply_broadcaster_effect(self, broadcaster, affected_object):
        if broadcaster.interaction is not None and self.excluded_participants is not None:
            participants = broadcaster.interaction.get_participants(
                self.excluded_participants)
            if affected_object in participants:
                return False
            if affected_object.sim_info is not None and affected_object.sim_info in participants:
                return False
        resolver = broadcaster.get_resolver(affected_object)
        if not self.tests.run_tests(resolver):
            return False
        return super()._should_apply_broadcaster_effect(
            broadcaster, affected_object)
Exemple #7
0
 def __init__(
         self,
         locked_animation_args=DEFAULT,
         animation_callback=DEFAULT,
         interaction_asm_type=DEFAULT,
         override_animation_context=False,
         participant_enum_override=DEFAULT,
         description='There is an arbitrary number of possible animation selectors, based on participation in group.',
         **kwargs):
     if participant_enum_override is DEFAULT:
         participant_enum_type = ParticipantTypeAnimation
         participant_enum_default = ParticipantTypeAnimation.Invalid
     else:
         participant_enum_type = participant_enum_override[0]
         participant_enum_default = participant_enum_override[1]
     super().__init__(groups=TunableList(
         TunableTuple(
             group=TunableEnumFlags(
                 participant_enum_type,
                 participant_enum_default,
                 description='The group the Sim must be a participant of'),
             animation_ref=TunableAnimationReference(
                 allow_reactionlets=False,
                 override_animation_context=override_animation_context,
                 callback=animation_callback)),
         description='A list of difficulty to animation mappings.'),
                      description=description,
                      **kwargs)
 def __init__(self, default_participant_type=None, **kwargs):
     super().__init__(participant_type=TunableEnumFlags(
         description=
         "\n                We use this participant's holiday tracker to get the icon.\n                ",
         enum_type=ParticipantTypeSingle,
         default=default_participant_type or ParticipantType.Actor),
                      **kwargs)
 def participant_type_override(participant_type_enum,
                               participant_type_default):
     return {
         'target_sim':
         TunableEnumFlags(
             description=
             '\n                    Target(s) of the relationship(s).\n                    ',
             enum_type=participant_type_enum,
             default=participant_type_default)
     }
Exemple #10
0
 def __init__(
         self,
         description='A buff that will get added to the subject when running the interaction if the tests succeeds.',
         **kwargs):
     super().__init__(
         subject=TunableEnumFlags(ParticipantType,
                                  ParticipantType.Actor,
                                  description='Who will receive the buff.'),
         tests=event_testing.tests.TunableTestSet(),
         buff_type=TunableBuffReference(
             description='The buff type to be added to the Sim.'),
         description=description,
         **kwargs)
Exemple #11
0
class OutfitGeneratorRandomizationMixin:
    INSTANCE_TUNABLES = {
        'filter_flag':
        TunableEnumFlags(
            description=
            '\n            Define how to handle part randomization for the generated outfit.\n            ',
            enum_type=OutfitFilterFlag,
            default=OutfitFilterFlag.USE_EXISTING_IF_APPROPRIATE
            | OutfitFilterFlag.USE_VALID_FOR_LIVE_RANDOM,
            allow_no_flags=True),
        'body_type_chance_overrides':
        TunableMapping(
            description=
            '\n            Define body type chance overrides for the generate outfit. For\n            example, if BODYTYPE_HAT is mapped to 100%, then the outfit is\n            guaranteed to have a hat if any hat matches the specified tags.\n            \n            If used in an appearance modifier, these body types will contribute\n            to the flags that determine which body types can be generated,\n            regardless of their percent chance.\n            ',
            key_type=BodyType,
            value_type=TunablePercent(
                description=
                '\n                The chance that a part is applied to the corresponding body\n                type.\n                ',
                default=100)),
        'body_type_match_not_found_policy':
        TunableMapping(
            description=
            '\n            The policy we should take for a body type that we fail to find a\n            match for. Primary example is to use MATCH_NOT_FOUND_KEEP_EXISTING\n            for generating a tshirt and making sure a sim wearing full body has\n            a lower body cas part.\n            \n            If used in an appearance modifier, these body types will contribute\n            to the flags that determine which body types can be generated.\n            ',
            key_type=BodyType,
            value_type=MatchNotFoundPolicy)
    }
    FACTORY_TUNABLES = INSTANCE_TUNABLES

    def get_body_type_flags(self):
        tuned_flags = 0
        for body_type in itertools.chain(
                self.body_type_chance_overrides.keys(),
                self.body_type_match_not_found_policy.keys()):
            tuned_flags |= 1 << body_type
        return tuned_flags or BodyTypeFlag.CLOTHING_ALL

    def _generate_outfit(self,
                         sim_info,
                         outfit_category,
                         outfit_index=0,
                         tag_list=(),
                         seed=None):
        sim_info.generate_outfit(
            outfit_category,
            outfit_index=outfit_index,
            tag_list=tag_list,
            filter_flag=self.filter_flag,
            body_type_chance_overrides=self.body_type_chance_overrides,
            body_type_match_not_found_overrides=self.
            body_type_match_not_found_policy,
            seed=seed)
 def __init__(self, **kwargs):
     super().__init__(
         description=
         '\n            A tunable that increments a specified statistic by a specified\n            amount, runs a sequence, and then decrements the statistic by the\n            same amount.\n            ',
         stat=Statistic.TunableReference(
             description=
             '\n                The statistic to increment and decrement.\n                '
         ),
         subject=TunableEnumFlags(
             description=
             '\n                The participant of the interaction on which the statistic will\n                be incremented and decremented.\n                ',
             enum_type=ParticipantType,
             default=ParticipantType.Object),
         transfer_stat=Statistic.TunableReference(
             description=
             '\n                The statistic whose value to transfer.\n                '
         ),
         transfer_subject=TunableEnumFlags(
             description=
             '\n                The participant of the interaction whose statistic value will\n                be transferred.\n                ',
             enum_type=ParticipantType,
             default=ParticipantType.Actor),
         **kwargs)
 def __init__(self, **kwargs):
     super().__init__(
         subject=TunableEnumFlags(
             ParticipantType,
             ParticipantType.Actor,
             description='Object the audio effect will be placed on.'),
         tag_name=Tunable(
             str,
             'x',
             description=
             'Name of the animation tag this effect will trigger on.'),
         effect_name=Tunable(
             str,
             None,
             description=
             'Name of the audio modification that will be applied'),
         **kwargs)
class SpawnerInteractionTuning(HasTunableSingletonFactory):
    __qualname__ = 'SpawnerInteractionTuning'
    FACTORY_TUNABLES = {
        'spawner_participant':
        TunableEnumFlags(
            description=
            '\n        Subject containing the spawner component the object reward data will \n        be read from.\n        ',
            enum_type=ParticipantType,
            default=ParticipantType.Object),
        'locked_args': {
            'spawn_type': ObjectRewardsTuning.SPAWNER_REWARD
        }
    }

    def __init__(self, spawner_participant, spawn_type, **kwargs):
        super().__init__()
        self.spawner_participant = spawner_participant
        self.spawn_type = spawn_type
Exemple #15
0
 def __init__(self, animation_callback=DEFAULT, allow_multi_si_cancel=False, allow_social_animation=False, locked_args=None, **kwargs):
     import interactions.base.basic
     animation_ref = TunableAnimationReference(callback=animation_callback, interaction_asm_type=InteractionAsmType.Outcome, description='The one-shot animation ref to play')
     animation_ref = OptionalTunable(animation_ref)
     if allow_multi_si_cancel:
         cancel_si = OptionalTunable(TunableEnumFlags(description='\n                                                                     Every participant in this list will have the SI\n                                                                     associated with the interaction owning this outcome\n                                                                     canceled.\n                                                                     ', enum_type=ParticipantType))
     else:
         cancel_si = TunableVariant(description="\n                                        When enabled, the outcome will cancel the owning\n                                        interaction's parent SI.\n                                        ", locked_args={'enabled': ParticipantType.Actor, 'disabled': None}, default='disabled')
     if allow_social_animation:
         kwargs['social_animation'] = OptionalTunable(description='\n                If enabled, specify an animation that will be played once for\n                the entire social group.\n                ', tunable=TunableAnimationReference())
     else:
         locked_args = {} if locked_args is None else locked_args
         locked_args['social_animation'] = None
     if locked_args is None:
         locked_args = {TunableOutcomeActions.ADD_TARGET_AFFORDANCE_LOOT_KEY: True}
     elif TunableOutcomeActions.ADD_TARGET_AFFORDANCE_LOOT_KEY not in locked_args:
         locked_args.update({TunableOutcomeActions.ADD_TARGET_AFFORDANCE_LOOT_KEY: True})
     super().__init__(animation_ref=animation_ref, xevt=OptionalTunable(description='\n                             When specified, the outcome will be associated to this xevent.\n                             ', tunable=Tunable(tunable_type=int, default=None)), response=OptionalTunable(TunableResponseSelector()), force_outcome_on_exit=Tunable(description='\n                             If checked outcome will always be given even if\n                             interaction was canceled. If unchecked, outcome\n                             will only be given if mixer, one_shot, or\n                             naturally finishing interaction.\n                             ', tunable_type=bool, default=False), loot_list=TunableList(description='\n                             A list of pre-defined loot operations.\n                             ', tunable=LootActions.TunableReference()), consume_object=Tunable(description="\n                             If checked, the loot list generated in the target\n                             object's consumable component will be added to\n                             this outcome's loot list.\n                             ", tunable_type=bool, default=False), continuation=TunableContinuation(description='An affordance to be pushed as part of an outcome.'), cancel_si=cancel_si, events_to_send=TunableList(TunableEnumEntry(TestEvent, TestEvent.Invalid, description='events types to send')), display_message=OptionalTunable(description='\n                             If set, flyaway text will be shown.\n                             ', tunable=TunableLocalizedString(default=None, description='Localized string that is shown as flyaway text')), loot_offset_override=OptionalTunable(Tunable(int, 0), description="An override to interactions.utils.loot's DEFAULT_LOOT_OFFSET tuning, specifically for this outcome."), parameterized_autonomy=TunableMapping(description='\n                             Specify parameterized autonomy for the participants of the interaction.\n                             ', key_type=TunableEnumEntry(description='\n                                 The participant to run parameterized autonomy for.\n                                 ', tunable_type=ParticipantType, default=ParticipantType.Actor), value_type=TunableList(description='\n                                 A list of parameterized autonomy requests to run.\n                                 ', tunable=TunableParameterizedAutonomy())), outcome_result=TunableEnumEntry(description='\n                             The interaction outcome result to consider this interaction result. This is\n                             important for testing interactions, such as an aspiration that wants to know\n                             if a Sim has successfully kissed another Sim. All interactions that a designer\n                             would consider a success case for such a scenario would be assured here.\n                             ', tunable_type=OutcomeResult, default=OutcomeResult.SUCCESS), basic_extras=interactions.base.basic.TunableBasicExtrasCore(), locked_args=locked_args, **kwargs)
Exemple #16
0
class JoinSituationElement(XevtTriggeredElement):
    FACTORY_TUNABLES = {
        'situation_type':
        TunableReference(
            description='\n            The situation to join.\n            ',
            manager=services.situation_manager()),
        'situation_job':
        OptionalTunable(
            description=
            '\n            The situation job that sim will get to while join the situation.\n            ',
            tunable=TunableReference(manager=services.situation_job_manager()),
            disabled_name='use_default_job',
            enabled_name='specify_job',
            disabled_value=DEFAULT),
        'subject':
        TunableEnumFlags(
            description=
            '\n            The participant of who will join the situation.\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.Actor)
    }

    def _do_behavior(self, *args, **kwargs):
        situation_manager = services.get_zone_situation_manager()
        situation = situation_manager.get_situation_by_type(
            self.situation_type)
        if situation is None:
            logger.error(
                'Fail to join situation since cannot find running situation {} for interaction {}',
                self.situation_type,
                self.interaction,
                owner='cjiang')
            return False
        subject = self.interaction.get_participant(self.subject)
        if subject is None or not subject.is_sim:
            logger.error(
                'Fail to join situation since subject {} is not sim for interaction {}',
                self.subject,
                self.interaction,
                owner='cjiang')
            return False
        situation.invite_sim_to_job(subject, job=self.situation_job)
        return True
Exemple #17
0
class CanvasComponent(Component, HasTunableFactory, component_name=objects.components.types.CANVAS_COMPONENT, persistence_key=persistence_protocols.PersistenceMaster.PersistableData.CanvasComponent):
    __qualname__ = 'CanvasComponent'
    FACTORY_TUNABLES = {'canvas_types': TunableEnumFlags(CanvasType, CanvasType.NONE, description="\n            A painting texture must support at least one of these canvas types\n            to be applied to this object's painting area.\n            ")}

    def __init__(self, owner, *, canvas_types):
        super().__init__(owner)
        self.canvas_types = canvas_types
        self._painting_state = None

    def save(self, persistence_master_message):
        persistable_data = persistence_protocols.PersistenceMaster.PersistableData()
        persistable_data.type = persistence_protocols.PersistenceMaster.PersistableData.CanvasComponent
        canvas_data = persistable_data.Extensions[persistence_protocols.PersistableCanvasComponent.persistable_data]
        canvas_data.texture_id = self.painting_state.texture_id
        canvas_data.reveal_level = self.painting_state.reveal_level
        persistence_master_message.data.extend([persistable_data])

    def load(self, persistable_data):
        canvas_data = persistable_data.Extensions[persistence_protocols.PersistableCanvasComponent.persistable_data]
        self.painting_state = PaintingState(canvas_data.texture_id, canvas_data.reveal_level)

    @distributor.fields.ComponentField(op=distributor.ops.SetPaintingState, default=None)
    def painting_state(self) -> PaintingState:
        return self._painting_state

    @painting_state.setter
    def painting_state(self, value):
        self._painting_state = value

    @property
    def painting_reveal_level(self) -> int:
        if self.painting_state is not None:
            return self.painting_state.reveal_level

    @painting_reveal_level.setter
    def painting_reveal_level(self, reveal_level):
        if self.painting_state is not None:
            self.painting_state = self.painting_state.get_at_level(reveal_level)

    @componentmethod_with_fallback(lambda msg: None)
    def populate_icon_canvas_texture_info(self, msg):
        if self.painting_state is not None and msg is not None:
            msg.texture_id = self.painting_state.texture_id
 def __init__(self, **kwargs):
     super().__init__(
         description=
         '\n            A tunable that increments a specified statistic by a specified\n            amount, runs a sequence, and then decrements the statistic by the\n            same amount.\n            ',
         stat=TunableReference(
             description=
             '\n                The statistic to increment and decrement.\n                ',
             manager=services.statistic_manager(),
             class_restrictions=(Statistic, Skill)),
         subject=TunableEnumFlags(
             description=
             '\n                The participant of the interaction on which the statistic will\n                be incremented and decremented.\n                ',
             enum_type=ParticipantType,
             default=ParticipantType.Object),
         amount=Tunable(
             description=
             '\n                The amount that will be incremented and decremented from the\n                specified statistic.\n                ',
             tunable_type=float,
             default=1),
         **kwargs)
 def __init__(self, **kwargs):
     super().__init__(
         subject=TunableEnumFlags(
             description=
             '\n                The participant of the interaction on which the statistic will\n                be incremented and decremented.\n                ',
             enum_type=ParticipantType,
             default=ParticipantType.Actor),
         rate=TunableRange(
             description=
             "\n                Units per second to remove of the target's commodities.",
             tunable_type=float,
             default=1,
             minimum=0,
             maximum=None),
         categories=TunableList(
             description=
             '\n                Will reduce all commodities that match any category in this list.',
             tunable=TunableEnumEntry(StatisticCategory,
                                      StatisticCategory.INVALID)),
         **kwargs)
Exemple #20
0
 def __init__(
         self,
         description="Configure focus on one or more of an interaction's participants.",
         **kwargs):
     super().__init__(
         subject=TunableEnumFlags(ParticipantType,
                                  ParticipantType.Object,
                                  description='Who or what to focus on.'),
         layer=Tunable(
             int,
             None,
             description=
             'Layer override: Ambient=0, SuperInteraction=3, Interaction=5.'
         ),
         score=Tunable(
             int,
             1,
             description=
             'Focus score.  This orders focus elements in the same layer.'),
         description=description,
         **kwargs)
    class _FilterByPortalFlags(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'portal_flags':
            TunableEnumFlags(
                description=
                '\n                The object must have any of these portal flags in order to\n                satisfy the requirement.\n                ',
                enum_type=PortalFlags)
        }

        def is_object_valid(self, obj):
            if getattr(obj, 'parts', None):
                return any(
                    self.is_object_valid(part.part_definition)
                    for part in obj.parts)
            surface_portal_constraint = getattr(obj,
                                                'surface_portal_constraint',
                                                None)
            if surface_portal_constraint is None:
                return False
            if surface_portal_constraint.required_portal_flags is None:
                return False
            return surface_portal_constraint.required_portal_flags & self.portal_flags
class PathPlanContextWrapper(HasTunableFactory, AutoFactoryInit,
                             PathPlanContext):
    class _AgentShapeCircle(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'radius':
            TunableRange(
                description=
                "\n                The circle's radius, The circle is built around the agent's\n                center point.\n                ",
                tunable_type=float,
                minimum=0,
                default=0.123)
        }

        def get_quadtree_polygon(self, position, orientation):
            return QtCircle(Vector2(position.x, position.z), self.radius)

    class _AgentShapeRectangle(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'length':
            TunableRange(
                description=
                "\n                The rectangle's length. This is parallel to the agent's forward\n                vector.\n                ",
                tunable_type=float,
                minimum=0,
                default=1.5),
            'width':
            TunableRange(
                description=
                "\n                The rectangle's width. This is perpendicular to the agent's\n                forward vector.\n                ",
                tunable_type=float,
                minimum=0,
                default=0.5)
        }

        def get_quadtree_polygon(self, position, orientation):
            length_vector = orientation.transform_vector(
                Vector3.Z_AXIS()) * self.length / 2
            return build_rectangle_from_two_points_and_radius(
                position + length_vector, position - length_vector, self.width)

    FACTORY_TUNABLES = {
        '_agent_radius':
        TunableRange(
            description=
            '\n            The size of the agent (as a circle radius), when computing a path.\n            This determines how much space is required for the agent to route\n            through space.\n            ',
            tunable_type=float,
            minimum=0,
            default=0.123),
        '_agent_shape':
        TunableVariant(
            description=
            "\n            The shape used to represent the agent's position and intended\n            position in the quadtree.\n            ",
            circle=_AgentShapeCircle.TunableFactory(),
            rectangle=_AgentShapeRectangle.TunableFactory(),
            default='circle'),
        '_agent_goal_radius':
        TunableRange(
            description=
            '\n            The clearance required (as a circle radius) at the end of a route.\n            ',
            tunable_type=float,
            minimum=0,
            default=0.123),
        '_agent_extra_clearance_modifier':
        TunableRange(
            description=
            "\n            The clearance that the agent will try to maintain from other objects\n            (and go around them), defined as a multiple of the agent's radius.\n            ",
            tunable_type=float,
            minimum=0,
            default=2.5),
        '_allowed_portal_flags':
        OptionalTunable(
            TunableEnumFlags(
                description=
                '\n                Required portal flags can be set on portals. If these flags are\n                also set on the actor, then that actor is allowed to traverse\n                that portal type.\n                \n                e.g. Counter Surface portals have the "Counter" flag set on the\n                portals. If the "Counter" flag is set on a routing context, then\n                your able to go through counter portals.\n                ',
                enum_type=PortalFlags)),
        '_discouraged_portal_flags':
        OptionalTunable(
            TunableEnumFlags(
                description=
                "\n                Flags to set on the Sim to match the discouragement flags on\n                a portal.  If the flags don't match, the Sim will be \n                discouraged from routing through this portal. \n                \n                e.g. Regular doors have a discouragement flag that matches\n                only humans, this way pets will try to use pet doors over \n                regular doors.\n                ",
                enum_type=PortalFlags)),
        '_allowed_heights':
        OptionalTunable(
            TunableEnumFlags(
                description=
                '\n                All of the flags that define what this agent is able to route\n                under. Each Flag has a specific height assigned to it.\n                \n                FOOTPRINT_KEY_REQUIRE_SMALL_HEIGHT = 0.5m\n                FOOTPRINT_KEY_REQUIRE_TINY_HEIGHT = 0.25m\n                FOOTPRINT_KEY_REQUIRE_FLOATING = flying agent\n                ',
                enum_type=FootprintKeyMaskBits,
                default=FootprintKeyMaskBits.SMALL_HEIGHT)),
        'surface_preference_scoring':
        TunableMapping(
            description=
            '\n            Surface type and additional score to apply to the cost of goals on \n            this surface.\n            This allows for geometric constraints with multi surface goals, to \n            have preference to specific surface types. i.e Cats always prefer\n            to target surface object goals, so we make the ground more \n            expensive.\n            This will only be applied for multi surface geometric constraints.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The surface type for scoring to be applied to.\n                ',
                tunable_type=routing.SurfaceType,
                default=routing.SurfaceType.SURFACETYPE_WORLD,
                invalid_enums=(routing.SurfaceType.SURFACETYPE_UNKNOWN, )),
            value_type=TunableRange(
                description=
                '\n                Additive cost to apply when calculating goals for multi surface \n                constraints.\n                ',
                tunable_type=float,
                default=10,
                minimum=0))
    }

    def __init__(self, agent, **kwargs):
        super().__init__(**kwargs)
        self._agent = agent
        self.agent_id = agent.id
        self.agent_radius = self._agent_radius
        self.agent_extra_clearance_multiplier = self._agent_extra_clearance_modifier
        self.agent_goal_radius = self._agent_goal_radius
        self.footprint_key = agent.definition.get_footprint(0)
        full_keymask = routing.FOOTPRINT_KEY_ON_LOT | routing.FOOTPRINT_KEY_OFF_LOT
        if self._allowed_heights is not None:
            full_keymask |= self._allowed_heights
        self.set_key_mask(full_keymask)
        full_portal_keymask = PortalFlags.DEFAULT
        species = getattr(self._agent, 'extended_species', DEFAULT)
        full_portal_keymask |= SpeciesExtended.get_portal_flag(species)
        age = getattr(self._agent, 'age', DEFAULT)
        full_portal_keymask |= Age.get_portal_flag(age)
        if self._allowed_portal_flags is not None:
            full_portal_keymask |= self._allowed_portal_flags
        self.set_portal_key_mask(full_portal_keymask)
        portal_discouragement_flags = 0
        if self._discouraged_portal_flags is not None:
            portal_discouragement_flags |= self._discouraged_portal_flags
        self.set_portal_discourage_key_mask(portal_discouragement_flags)

    def get_quadtree_polygon(self, position=DEFAULT, orientation=DEFAULT):
        position = self._agent.position if position is DEFAULT else position
        orientation = self._agent.orientation if orientation is DEFAULT else orientation
        return self._agent_shape.get_quadtree_polygon(position, orientation)

    def add_location_to_quadtree(self,
                                 placement_type,
                                 position=DEFAULT,
                                 orientation=DEFAULT,
                                 routing_surface=DEFAULT,
                                 index=0):
        position = self._agent.position if position is DEFAULT else position
        orientation = self._agent.orientation if orientation is DEFAULT else orientation
        routing_surface = self._agent.routing_surface if routing_surface is DEFAULT else routing_surface
        if placement_type in (placement.ItemType.SIM_POSITION,
                              placement.ItemType.SIM_INTENDED_POSITION):
            quadtree_geometry = self.get_quadtree_polygon(
                position=position, orientation=orientation)
        else:
            quadtree_geometry = QtCircle(Vector2(position.x, position.z),
                                         self.agent_goal_radius)
        services.sim_quadtree().insert(self._agent, self._agent.id,
                                       placement_type, quadtree_geometry,
                                       routing_surface, False, index)

    def remove_location_from_quadtree(self, placement_type, index=0):
        services.sim_quadtree().remove(self._agent.id, placement_type, index)
 def __init__(self, **kwargs):
     super().__init__(participant_type=TunableEnumFlags(
         ParticipantType, ParticipantType.Actor),
                      description="The Sim who's thumbnail will be used.",
                      **kwargs)
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)
class WalksStyleBehavior(HasTunableSingletonFactory, AutoFactoryInit):
    SWIMMING_WALKSTYLES = TunableList(
        description=
        '\n        The exhaustive list of walkstyles allowed while Sims are swimming. If a\n        Sim has a request for a walkstyle that is not supported, the first\n        element is used as a replacement.\n        ',
        tunable=TunableWalkstyle(pack_safe=True))
    WALKSTYLE_COST = TunableMapping(
        description=
        '\n        Associate a specific walkstyle to a statistic cost before the walkstyle\n        can be activated.\n        ',
        key_type=TunableWalkstyle(
            description=
            '\n            The walkstyle that should have a specified cost when triggered.\n            ',
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            Cost data of the specified walkstyle.\n            ',
            walkstyle_cost_statistic=TunableReference(
                description=
                '\n                The statistic we are operating on when the walkstyle is\n                triggered.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.STATISTIC),
                pack_safe=True),
            cost=TunableRange(
                description=
                '\n                When the walkstyle is triggered during a route, this is the\n                cost that will be substracted from the specified statistic. \n                ',
                tunable_type=int,
                default=1,
                minimum=0)))
    WALKSTYLES_OVERRIDE_TELEPORT = TunableList(
        description=
        '\n        Any walkstyles found here will be able to override the teleport styles\n        if they are specified.\n        ',
        tunable=TunableWalkstyle(pack_safe=True))
    FACTORY_TUNABLES = {
        'carry_walkstyle_behavior':
        OptionalTunable(
            description=
            '\n            Define the walkstyle the Sim plays whenever they are being carried\n            by another Sim.\n            \n            If this is set to "no behavior", the Sim will not react to the\n            parent\'s walkstyle at all. They will play no walkstyle, and rely on\n            posture idles to animate.\n            \n            If this is set, Sims have the ability to modify their walkstyle\n            whenever their parent is routing.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Specify how this Sim behaves when being carried by another Sim.\n                ',
                default_carry_walkstyle=TunableWalkstyle(
                    description=
                    '\n                    Unless an override is specified, this is the walkstyle\n                    applied to the Sim whenever they are being carried.\n                    '
                ),
                carry_walkstyle_overrides=TunableMapping(
                    description=
                    '\n                    Define carry walkstyle overrides. For instance, we might\n                    want to specify a different carry walkstyle if the parent is\n                    frantically running, for example.\n                    ',
                    key_type=TunableWalkstyle(
                        description=
                        '\n                        The walkstyle that this carry walkstyle override applies to.\n                        ',
                        pack_safe=True),
                    value_type=TunableWalkstyle(
                        description=
                        '\n                        The carry walkstyle override.\n                        ',
                        pack_safe=True))),
            enabled_name='Apply_Carry_Walkstyle',
            disabled_name='No_Behavior'),
        'combo_walkstyle_replacements':
        TunableList(
            description=
            '\n            The prioritized list of the combo walkstyle replacement rules. We\n            use this list to decide if a Sim should use a combo walk style based\n            on the the highest priority walkstyle request, and other walkstyles\n            that might affect the replacement based on the key combo rules.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The n->1 mapping of walkstyle replacement. \n                ',
                key_combo_list=TunableList(
                    description=
                    '\n                    The list of the walkstyles used as key combos. If the\n                    current highest priority walkstyle exists in this list, and\n                    the Sim has every other walkstyle in the key list, then we\n                    replace this with the result walkstyle tuned in the tuple.\n                    ',
                    tunable=TunableWalkstyle(pack_safe=True)),
                result=TunableWalkstyle(
                    description=
                    '\n                    The mapped combo walkstyle.\n                    ',
                    pack_safe=True))),
        'default_walkstyle':
        TunableWalkstyle(
            description=
            '\n            The underlying walkstyle for this Sim. This is most likely going to\n            be overridden by the CAS walkstyle, emotional walkstyles, buff\n            walkstyles, etc...\n            '
        ),
        'run_allowed_flags':
        TunableEnumFlags(
            description=
            "\n            Define where the Sim is allowed to run. Certain buffs might suppress\n            a Sim's ability to run.\n            ",
            enum_type=WalkStyleRunAllowedFlags,
            default=WalkStyleRunAllowedFlags.RUN_ALLOWED_OUTDOORS,
            allow_no_flags=True),
        'run_disallowed_walkstyles':
        TunableList(
            description=
            "\n            A set of walkstyles that would never allow a Sim to run, i.e., if\n            the Sim's requested walkstyle is in this set, they will not run,\n            even to cover great distances.\n            ",
            tunable=TunableWalkstyle(pack_safe=True)),
        'run_required_total_distance':
        TunableRange(
            description=
            '\n            For an entire route, the minimum distance required for Sim to run.\n            ',
            tunable_type=float,
            minimum=0,
            default=20),
        'run_required_segment_distance':
        TunableRange(
            description=
            '\n            For a specific route segment, the minimum distance required for the\n            Sim to run.\n            ',
            tunable_type=float,
            minimum=0,
            default=10),
        'run_walkstyle':
        TunableWalkstyle(
            description=
            '\n            The walkstyle to use when this Sim is supposed to be running.\n            '
        ),
        'wading_walkstyle':
        OptionalTunable(
            description=
            '\n            If enabled, the routing agent will play a different walkstyle when\n            walking through water.\n            ',
            tunable=TunableWalkstyle(
                description=
                '\n                The walkstyle to use when wading through water.\n                '
            )),
        'wading_walkstyle_buff':
        OptionalTunable(
            description=
            '\n            A buff which, if tuned, will be on the sim if the sim is currently\n            in wading level water.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.BUFF))),
        'short_walkstyle':
        TunableWalkstyle(
            description=
            '\n            The walkstyle to use when Sims are routing over a distance shorter\n            than the one defined in "Short Walkstyle Distance" or any of the\n            overrides.\n            \n            This value is used if no override is tuned in "Short Walkstyle Map".\n            '
        ),
        'short_walkstyle_distance':
        TunableRange(
            description=
            "\n            Any route whose distance is less than this value will request the\n            short version of the Sim's current walkstyle.\n            ",
            tunable_type=float,
            minimum=0,
            default=7),
        'short_walkstyle_distance_override_map':
        TunableMapping(
            description=
            "\n            If a Sim's current walkstyle is any of the ones specified in here,\n            use the associated value to determine if the short version of the\n            walkstyle is to be requested.\n            ",
            key_type=TunableWalkstyle(
                description=
                '\n                The walkstyle that this distance override applies to.\n                ',
                pack_safe=True),
            value_type=TunableRange(
                description=
                "\n                Any route whose distance is less than this value will request\n                the short version of the Sim's current walkstyle, provided the\n                Sim's current walkstyle is the associated walkstyle.\n                ",
                tunable_type=float,
                minimum=0,
                default=7)),
        'short_walkstyle_map':
        TunableMapping(
            description=
            '\n            Associate a specific short version of a walkstyle to walkstyles.\n            ',
            key_type=TunableWalkstyle(
                description=
                '\n                The walkstyle that this short walkstyle mapping applies to.\n                ',
                pack_safe=True),
            value_type=TunableWalkstyle(
                description=
                '\n                The short version of the associated walkstyle.\n                ',
                pack_safe=True))
    }

    def _get_walkstyle_overrides(self, actor):
        if actor.is_sim:
            return tuple(buff.walkstyle_behavior_override
                         for buff in actor.get_active_buff_types()
                         if buff.walkstyle_behavior_override is not None)
        return ()

    def _apply_run_walkstyle_to_path(self,
                                     actor,
                                     path,
                                     walkstyle_overrides,
                                     time_offset=None):
        run_allowed_flags = self.run_allowed_flags
        for walkstyle_override in walkstyle_overrides:
            run_allowed_flags |= walkstyle_override.additional_run_flags
        for walkstyle_override in walkstyle_overrides:
            run_allowed_flags &= ~walkstyle_override.removed_run_flags
        if not run_allowed_flags:
            return
        run_required_total_distance = sims4.math.safe_max(
            (override for override in walkstyle_overrides
             if override.run_required_total_distance is not None),
            key=operator.attrgetter('walkstyle_behavior_priority'),
            default=self).run_required_total_distance
        if path.length() < run_required_total_distance:
            return
        run_required_segment_distance = sims4.math.safe_max(
            (override for override in walkstyle_overrides
             if override.run_required_segment_distance is not None),
            key=operator.attrgetter('walkstyle_behavior_priority'),
            default=self).run_required_segment_distance
        path_nodes = list(path.nodes)
        all_path_node_data = []
        for (start_node, end_node) in zip(path_nodes, path_nodes[1:]):
            switch_routing_surface = start_node.routing_surface_id != end_node.routing_surface_id
            is_outside = start_node.portal_id == 0 and get_block_id_for_node(
                start_node) == 0
            route_key = (switch_routing_surface, is_outside)
            all_path_node_data.append((route_key, start_node, end_node))
        run_walkstyle = self.get_run_walkstyle(actor)
        for ((_, is_outside),
             path_node_data) in itertools.groupby(all_path_node_data,
                                                  key=operator.itemgetter(0)):
            if is_outside and not run_allowed_flags & WalkStyleRunAllowedFlags.RUN_ALLOWED_OUTDOORS:
                continue
            if not is_outside and not run_allowed_flags & WalkStyleRunAllowedFlags.RUN_ALLOWED_INDOORS:
                continue
            path_node_data = list(path_node_data)
            segment_length = sum(
                (sims4.math.Vector3(*start_node.position) -
                 sims4.math.Vector3(*end_node.position)).magnitude_2d()
                for (_, start_node, end_node) in path_node_data)
            if segment_length < run_required_segment_distance:
                continue
            for (_, path_node, _) in path_node_data:
                if not time_offset is None:
                    if path_node.time >= time_offset:
                        path_node.walkstyle = run_walkstyle
                path_node.walkstyle = run_walkstyle

    def check_for_wading(self, sim, *_, **__):
        routing_component = sim.routing_component
        if not routing_component.last_route_has_wading_nodes and not routing_component.wading_buff_handle:
            return
        wading_interval = OceanTuning.get_actor_wading_interval(sim)
        if wading_interval is None:
            return
        water_height = get_water_depth_at_location(sim.location)
        if water_height in wading_interval:
            if routing_component.wading_buff_handle is None:
                routing_component.wading_buff_handle = sim.add_buff(
                    self.wading_walkstyle_buff)
        elif routing_component.wading_buff_handle is not None:
            sim.remove_buff(routing_component.wading_buff_handle)
            routing_component.wading_buff_handle = None

    def _apply_wading_walkstyle_to_path(self,
                                        actor,
                                        path,
                                        default_walkstyle,
                                        time_offset=None):
        if actor.is_sim and actor.sim_info.is_ghost:
            return False
        wading_interval = OceanTuning.get_actor_wading_interval(actor)
        if wading_interval is None:
            return False
        wading_walkstyle = self.get_wading_walkstyle(actor)
        if wading_walkstyle is None:
            return False

        def get_node_water_height(path_node):
            return get_water_depth(path_node.position[0],
                                   path_node.position[2],
                                   path_node.routing_surface_id.secondary_id)

        path_nodes = list(path.nodes)
        start_wading = get_node_water_height(path_nodes[0]) in wading_interval
        end_wading = get_node_water_height(path_nodes[-1]) in wading_interval
        if not start_wading and not end_wading:
            return False
        path_contains_wading = False
        for (start_node, end_node) in zip(path_nodes, path_nodes[1:]):
            if time_offset is not None and end_node.time < time_offset:
                continue
            if start_node.routing_surface_id.type == SurfaceType.SURFACETYPE_POOL:
                continue
            if start_node.portal_object_id != 0:
                continue
            start_wading = get_node_water_height(start_node) in wading_interval
            end_wading = get_node_water_height(end_node) in wading_interval
            if not start_wading and not end_wading:
                continue
            is_wading = start_wading
            if is_wading:
                start_node.walkstyle = wading_walkstyle
                path_contains_wading = True
            nodes_to_add = []
            for (transform, routing_surface,
                 time) in path.get_location_data_along_segment_gen(
                     start_node.index, end_node.index, time_step=0.3):
                should_wade = get_water_depth(
                    transform.translation[0],
                    transform.translation[2]) in wading_interval
                if is_wading and not should_wade:
                    is_wading = False
                    nodes_to_add.append(
                        (Location(transform.translation, transform.orientation,
                                  routing_surface), time, 0, default_walkstyle,
                         0, 0, end_node.index))
                elif not is_wading:
                    if should_wade:
                        is_wading = True
                        nodes_to_add.append(
                            (Location(transform.translation,
                                      transform.orientation, routing_surface),
                             time, 0, wading_walkstyle, 0, 0, end_node.index))
                        path_contains_wading = True
            running_index_offset = 0
            for (loc, time, node_type, walkstyle, portal_obj_id, portal_id,
                 index) in nodes_to_add:
                node_index = index + running_index_offset
                path.nodes.add_node(loc, time, node_type, walkstyle,
                                    portal_obj_id, portal_id, node_index)
                node = path.nodes[node_index]
                node.is_procedural = False
                running_index_offset += 1
        return path_contains_wading

    def apply_walkstyle_to_path(self, actor, path, time_offset=None):
        gsi_archiver = None
        can_archive = gsi_handlers.walkstyle_handlers.archiver.enabled and actor.is_sim
        if can_archive:
            gsi_archiver = WalkstyleGSIArchiver(actor)
        walkstyle = self.get_walkstyle_for_path(actor, path, gsi_archiver)
        if can_archive:
            gsi_archiver.gsi_archive_entry()
        path_nodes = list(path.nodes)
        for path_node in path_nodes:
            if not time_offset is None:
                if path_node.time >= time_offset:
                    path_node.walkstyle = walkstyle
            path_node.walkstyle = walkstyle
        walkstyle_overrides = self._get_walkstyle_overrides(actor)
        if walkstyle not in self.run_disallowed_walkstyles:
            self._apply_run_walkstyle_to_path(actor,
                                              path,
                                              walkstyle_overrides,
                                              time_offset=time_offset)
        if actor.is_sim:
            actor.routing_component.last_route_has_wading_nodes = self._apply_wading_walkstyle_to_path(
                actor, path, walkstyle, time_offset=time_offset)
        return walkstyle

    def get_combo_replacement(self, highest_priority_walkstyle,
                              walkstyle_list):
        for combo_tuple in self.combo_walkstyle_replacements:
            key_combo_list = combo_tuple.key_combo_list
            if highest_priority_walkstyle in key_combo_list:
                if all(ws in walkstyle_list for ws in key_combo_list):
                    return combo_tuple

    def get_combo_replaced_walkstyle(self, highest_priority_walkstyle,
                                     walkstyle_list):
        combo_tuple = self.get_combo_replacement(highest_priority_walkstyle,
                                                 walkstyle_list)
        if combo_tuple is not None:
            return combo_tuple.result

    def get_default_walkstyle(self, actor, gsi_archiver=None):
        walkstyle = actor.get_cost_valid_walkstyle(
            WalksStyleBehavior.WALKSTYLE_COST)
        walkstyle_list = actor.get_walkstyle_list()
        replaced_walkstyle = self.get_combo_replaced_walkstyle(
            walkstyle, walkstyle_list)
        if gsi_archiver is not None:
            gsi_archiver.default_walkstyle = walkstyle
            gsi_archiver.combo_replacement_walkstyle_found = replaced_walkstyle
        if replaced_walkstyle is not None:
            walkstyle = replaced_walkstyle
        return walkstyle

    def get_short_walkstyle(self, walkstyle, actor):
        short_walkstyle = self._get_property_override(actor, 'short_walkstyle')
        return self.short_walkstyle_map.get(walkstyle, short_walkstyle)

    def get_run_walkstyle(self, actor):
        run_walkstyle = self._get_property_override(actor, 'run_walkstyle')
        return run_walkstyle

    def get_wading_walkstyle(self, actor):
        wading_walkstyle = self._get_property_override(actor,
                                                       'wading_walkstyle')
        return wading_walkstyle

    def supports_wading_walkstyle_buff(self, actor):
        return self.wading_walkstyle_buff is not None and OceanTuning.get_actor_wading_interval(
            actor)

    def _get_property_override(self, actor, property_name):
        overrides = self._get_walkstyle_overrides(actor)
        override = sims4.math.safe_max(
            (override for override in overrides
             if getattr(override, property_name) is not None),
            key=operator.attrgetter('walkstyle_behavior_priority'),
            default=self)
        property_value = getattr(override, property_name)
        return property_value

    def _apply_walkstyle_cost(self, actor, walkstyle):
        walkstyle_cost = WalksStyleBehavior.WALKSTYLE_COST.get(walkstyle, None)
        if walkstyle_cost is not None:
            stat_instance = actor.get_stat_instance(
                walkstyle_cost.walkstyle_cost_statistic)
            if stat_instance is None:
                logger.error(
                    'Statistic {}, not found on Sim {} for walkstyle cost',
                    walkstyle_cost.walkstyle_cost_statistic,
                    actor,
                    owner='camilogarcia')
                return
            stat_instance.add_value(-walkstyle_cost.cost)

    def get_walkstyle_for_path(self, actor, path, gsi_archiver=None):
        walkstyle = self.get_default_walkstyle(actor, gsi_archiver)
        if gsi_archiver is not None:
            gsi_archiver.walkstyle_requests = actor.routing_component.get_walkstyle_requests(
            )
        short_walk_distance = self.short_walkstyle_distance_override_map.get(
            walkstyle, self.short_walkstyle_distance)
        if path.length() < short_walk_distance:
            walkstyle = self.get_short_walkstyle(walkstyle, actor)
            if gsi_archiver is not None:
                gsi_archiver.default_walkstyle_replaced_by_short_walkstyle = walkstyle
        if actor.is_sim:
            if actor.in_pool and walkstyle not in self.SWIMMING_WALKSTYLES:
                walkstyle = self.SWIMMING_WALKSTYLES[0]
                if gsi_archiver is not None:
                    gsi_archiver.default_walkstyle_replaced_by_swimming_walkstyle = walkstyle
                return walkstyle
            else:
                posture = actor.posture
                if posture.mobile and posture.compatible_walkstyles and walkstyle not in posture.compatible_walkstyles:
                    walkstyle = posture.compatible_walkstyles[0]
                    if gsi_archiver is not None:
                        gsi_archiver.default_walkstyle_replaced_by_posture_walkstyle = walkstyle
                    return walkstyle
        return walkstyle
Exemple #26
0
class DoorService(Service):
    FRONT_DOOR_ALLOWED_PORTAL_FLAGS = TunableEnumFlags(
        description=
        "\n        Door Service does a routability check to all doors from the lot's\n        arrival spawn point to find doors that are reachable without crossing\n        other doors.\n        \n        These flags are supplied to the routability check's PathPlanContext, to\n        tell it what portals are usable. For example, stair portals should be\n        allowed (e.g. for front doors off the ground level, or house is on a\n        foundation).\n        ",
        enum_type=PortalFlags)
    FRONT_DOOR_ALLOWED_APARTMENT_PORTAL_FLAGS = TunableEnumFlags(
        description=
        '\n        Additional portal flags to be used if needing to choose between\n        multiple external doors in an apartment.\n        \n        e.g. Elevator.\n        ',
        enum_type=PortalFlags)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._front_door_id = None
        self._plex_door_infos = EMPTY_SET

    @classproperty
    def save_error_code(cls):
        return persistence_error_types.ErrorCodes.SERVICE_SAVE_FAILED_DOOR_SERVICE

    def has_front_door(self):
        return self._front_door_id is not None

    def get_front_door(self):
        return services.object_manager().get(self._front_door_id)

    def on_door_removed(self, door):
        if door.id == self._front_door_id:
            self._front_door_id = None

    def fix_up_doors(self, force_refresh=False):
        building_type = services.get_plex_service().get_plex_building_type(
            services.current_zone_id())
        if building_type == PlexBuildingType.DEFAULT or building_type == PlexBuildingType.PENTHOUSE_PLEX or building_type == PlexBuildingType.COASTAL:
            self._fix_up(force_refresh=force_refresh)
        elif building_type == PlexBuildingType.FULLY_CONTAINED_PLEX:
            self._fix_up_for_apartments()
        services.object_manager().on_front_door_candidates_changed()

    def _fix_up(self, force_refresh):
        (exterior_door_infos,
         interior_doors) = self._get_exterior_and_interior_doors()
        backward_doors = set(info.door for info in exterior_door_infos
                             if info.is_backwards)
        self._flip_backward_doors(backward_doors)
        self._set_front_door_availabilities(exterior_door_infos,
                                            interior_doors)
        preferred_door_id = self._front_door_id if not force_refresh else None
        new_front_door = self._choose_front_door(
            exterior_door_infos, preferred_door_id=preferred_door_id)
        self.set_as_front_door(new_front_door)

    def set_as_front_door(self, door):
        if door is None and self._front_door_id is None:
            return
        if door is not None and self._front_door_id == door.id:
            return
        old_door = services.object_manager().get(self._front_door_id)
        if old_door is not None:
            old_door.set_front_door_status(False)
        self._front_door_id = None
        if door is not None:
            door.set_front_door_status(True)
            self._front_door_id = door.id

    def _flip_backward_doors(self, doors):
        for door in doors:
            door.swap_there_and_back()

    def _set_front_door_availabilities(self, exterior_door_infos,
                                       interior_doors):
        zone_requires_front_door = self._zone_requires_front_door()
        for info in exterior_door_infos:
            info.door.set_front_door_availability(zone_requires_front_door)
        for door in interior_doors:
            door.set_front_door_availability(False)

    def _zone_requires_front_door(self):
        zone = services.current_zone()
        venue = zone.venue_service.active_venue
        requires_front_door = venue.venue_requires_front_door
        if requires_front_door == VenueFrontdoorRequirement.NEVER:
            return False
        if requires_front_door == VenueFrontdoorRequirement.ALWAYS:
            return True
        if requires_front_door == VenueFrontdoorRequirement.OWNED_OR_RENTED:
            if services.travel_group_manager().is_current_zone_rented():
                return True
            return zone.lot.owner_household_id != 0
        logger.error(
            'Current venue {} at Zone {} has front door requirement set to invalid value: {}',
            venue,
            zone,
            requires_front_door,
            owner='trevor')
        return False

    def _choose_front_door(self, exterior_door_infos, preferred_door_id=None):
        if not self._zone_requires_front_door():
            return
        if preferred_door_id is not None:
            for info in exterior_door_infos:
                if info.door.id == preferred_door_id:
                    return info.door
        if not exterior_door_infos:
            return
        info = min(exterior_door_infos, key=operator.attrgetter('distance'))
        return info.door

    def _object_is_door(self, obj):
        if not isinstance(obj, Door):
            return False
        elif not obj.is_door_portal:
            return False
        return True

    def _get_doors(self):
        doors = frozenset(obj for obj in services.object_manager().values()
                          if self._object_is_door(obj))
        return doors

    def _get_arrival_point(self):
        zone = services.current_zone()
        spawn_point = zone.active_lot_arrival_spawn_point
        if spawn_point is not None:
            return spawn_point.get_approximate_center()
        logger.error(
            'Active lot missing lot arrival spawn points. This will cause incorrect front door behavior.',
            zone.lot.lot_id)
        return zone.lot.corners[1]

    def _get_exterior_and_interior_doors(self):
        doors = self._get_doors()
        connections = self._get_door_connections_from_arrival(doors)
        exterior_door_to_infos = {}
        for (_, handle, distance) in connections:
            old_info = exterior_door_to_infos.get(handle.door)
            if old_info is not None and not handle.is_front:
                continue
            is_backwards = not handle.is_front
            info = ExteriorDoorInfo(door=handle.door,
                                    distance=distance,
                                    is_backwards=is_backwards)
            exterior_door_to_infos[handle.door] = info
        interior_doors = frozenset(door for door in doors
                                   if door not in exterior_door_to_infos)
        return (frozenset(exterior_door_to_infos.values()), interior_doors)

    def _get_door_connections_from_arrival(self, doors, is_apartment=False):
        zone = services.current_zone()
        source_point = self._get_arrival_point()
        source_handles = set()
        source_handle = Handle(
            source_point,
            routing.SurfaceIdentifier(zone.id, 0,
                                      routing.SurfaceType.SURFACETYPE_WORLD))
        source_handles.add(source_handle)
        routing_context = routing.PathPlanContext()
        for door in doors:
            for portal_handle in door.get_portal_pairs():
                routing_context.lock_portal(portal_handle.there)
                routing_context.lock_portal(portal_handle.back)
        routing_context.set_key_mask(routing.FOOTPRINT_KEY_ON_LOT
                                     | routing.FOOTPRINT_KEY_OFF_LOT)
        if is_apartment:
            routing_context.set_portal_key_mask(
                DoorService.FRONT_DOOR_ALLOWED_PORTAL_FLAGS
                | DoorService.FRONT_DOOR_ALLOWED_APARTMENT_PORTAL_FLAGS)
        else:
            routing_context.set_portal_key_mask(
                DoorService.FRONT_DOOR_ALLOWED_PORTAL_FLAGS)
        dest_handles = set()
        for door in doors:
            (front_position, back_position) = door.get_door_positions()
            if not front_position is None:
                if back_position is None:
                    continue
                dest_handles.add(
                    DoorConnectivityHandle(front_position,
                                           door.routing_surface,
                                           door=door,
                                           is_front=True))
                dest_handles.add(
                    DoorConnectivityHandle(back_position,
                                           door.routing_surface,
                                           door=door,
                                           is_front=False))
        connections = ()
        if dest_handles:
            connections = routing.estimate_path_batch(
                source_handles, dest_handles, routing_context=routing_context)
            if connections is None:
                connections = ()
        return connections

    def _fix_up_for_apartments(self):
        plex_door_infos = self.get_plex_door_infos(force_refresh=True)
        backward_doors = set()
        active_zone_id = services.current_zone_id()
        object_manager = services.object_manager()
        plex_doors = []
        for info in plex_door_infos:
            household_id = services.get_persistence_service(
            ).get_household_id_from_zone_id(info.zone_id)
            door = object_manager.get(info.door_id)
            if door is None:
                logger.error('Plex Door {} does not exist.',
                             info.door_id,
                             owner='rmccord')
            else:
                if info.is_backwards:
                    backward_doors.add(door)
                    current_zone = services.current_zone()
                    lot = current_zone.lot
                    world_description_id = services.get_world_description_id(
                        current_zone.world_id)
                    lot_description_id = services.get_lot_description_id(
                        lot.lot_id, world_description_id)
                    neighborhood_id = current_zone.neighborhood_id
                    neighborhood_data = services.get_persistence_service(
                    ).get_neighborhood_proto_buff(neighborhood_id)
                    logger.error(
                        'For WB: An apartment door facing the common area needs to be flipped. Lot desc id: {}, World desc id: {}. Neighborhood id: {}, Neighborhood Name: {}',
                        lot_description_id, world_description_id,
                        neighborhood_id, neighborhood_data.name)
                door.set_household_owner_id(household_id)
                if info.zone_id == active_zone_id:
                    plex_doors.append(door)
                else:
                    door.set_inactive_apartment_door_status(True)
        self._flip_backward_doors(backward_doors)
        if not plex_doors:
            return
        if len(plex_doors) == 1:
            self.set_as_front_door(plex_doors[0])
            return
        logger.warn(
            "plex zone_id: {} has multiple potential front doors: {}, can lead to sims able to access areas they shouldn't",
            active_zone_id, plex_doors)
        best_door = None
        best_distance = None
        connections = self._get_door_connections_from_arrival(
            plex_doors, is_apartment=True)
        for (_, handle, distance) in connections:
            if not best_distance is None:
                if best_distance < distance:
                    best_door = handle.door
                    best_distance = distance
            best_door = handle.door
            best_distance = distance
        if best_door is None:
            logger.error(
                'Unable to route to plex doors in zone_id: {} potential doors: {}',
                active_zone_id, plex_doors)
            self.set_as_front_door(plex_doors[0])
            return
        self.set_as_front_door(best_door)

    def unlock_all_doors(self):
        doors = self._get_doors()
        for door in doors:
            door.remove_locks()

    def get_plex_door_infos(self, force_refresh=False):
        if self._plex_door_infos and not force_refresh:
            return self._plex_door_infos
        plex_service = services.get_plex_service()
        doors = self._get_doors()
        plex_door_infos = set()
        for door in doors:
            (front_position, back_position) = door.get_door_positions()
            if front_position is None or back_position is None:
                logger.error("Door '{}' has broken portals.", door)
            else:
                front_zone_id = plex_service.get_plex_zone_at_position(
                    front_position, door.level)
                back_zone_id = plex_service.get_plex_zone_at_position(
                    back_position, door.level)
                if front_zone_id is None and back_zone_id is None:
                    current_zone = services.current_zone()
                    lot = current_zone.lot
                    world_description_id = services.get_world_description_id(
                        current_zone.world_id)
                    lot_description_id = services.get_lot_description_id(
                        lot.lot_id, world_description_id)
                    neighborhood_id = current_zone.neighborhood_id
                    neighborhood_data = services.get_persistence_service(
                    ).get_neighborhood_proto_buff(neighborhood_id)
                    if False and EXEMPT_DOOR_WORLD_DESCRIPTION_ID == world_description_id and EXEMPT_DOOR_LOT_DESCRIPTION_ID == lot_description_id:
                        continue
                    logger.error(
                        "Door isn't part of any plex. This will require WB fix. Door: {}, Lot desc id: {}, World desc id: {}. Neighborhood id: {}, Neighborhood Name: {}",
                        door, lot_description_id, world_description_id,
                        neighborhood_id, neighborhood_data.name)
                else:
                    if front_zone_id == back_zone_id:
                        continue
                    zone_id = front_zone_id or back_zone_id
                    is_backwards = front_zone_id is not None
                    info = PlexDoorInfo(door_id=door.id,
                                        zone_id=zone_id,
                                        is_backwards=is_backwards)
                    plex_door_infos.add(info)
        self._plex_door_infos = frozenset(plex_door_infos)
        return self._plex_door_infos

    def save(self, zone_data=None, **kwargs):
        if zone_data is not None:
            if self._front_door_id is not None:
                zone_data.front_door_id = self._front_door_id

    def load(self, zone_data=None):
        for door in self._get_doors():
            door.set_front_door_status(False)
            door.set_front_door_availability(False)
            door.set_inactive_apartment_door_status(False)
        if zone_data is not None and zone_data.HasField('front_door_id'):
            door = services.object_manager().get(zone_data.front_door_id)
            if door is not None:
                self.set_as_front_door(door)
Exemple #27
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
Exemple #28
0
 def __init__(self, description='Generate a privacy region for this object', callback=None, **kwargs):
     super().__init__(tests=TunableTestSet(description='\n                Any Sim who passes these tests will be allowed to violate the\n                privacy region.\n                '), shoo_exempt_tests=TunableTestSet(description='\n                Any violator who passes these tests will still be considered a\n                violator, but ill be exempt from being shooed.\n                i.e. A cat will get shooed when it breaks a privacy region, but\n                cats will ignore the shoo behavior.\n                '), max_line_of_sight_radius=Tunable(description='\n                The maximum possible distance from this object than an\n                interaction can reach.\n                ', tunable_type=float, default=5), map_divisions=Tunable(description='\n                The number of points around the object to check collision from.\n                More points means higher accuracy.\n                ', tunable_type=int, default=30), simplification_ratio=Tunable(description='\n                A factor determining how much to combine edges in the line of\n                sight polygon.\n                ', tunable_type=float, default=0.25), boundary_epsilon=Tunable(description='\n                The LOS origin is allowed to be outside of the boundary by this\n                amount.\n                ', tunable_type=float, default=0.01), facing_offset=Tunable(description='\n                The LOS origin is offset from the object origin by this amount\n                (mainly to avoid intersecting walls).\n                ', tunable_type=float, default=0.1), routing_surface_only=Tunable(description="\n                If this is checked, then the privacy constraint is generated on\n                the surface defined by the interaction's target. If the\n                interaction has no target or the target does not provide a\n                routable surface, no privacy is generated.\n                \n                Furthermore, privacy that is exclusive to routing surface will\n                only shoo Sims that are on the routable surface.\n                \n                e.g. A Sim cleaning a counter needs to shoo cats on the counter.\n                \n                The default behavior is for privacy to be generated on the\n                surface the Sim is on, and for it to apply to Sims on all\n                surfaces.\n                \n                e.g. A Sim using the toilet would shoo cats within the privacy\n                region that happen to be on routable surfaces, such as counters.\n                ", tunable_type=bool, default=False), shoo_constraint_radius=OptionalTunable(description='\n                If enabled, you can tune a specific radius for the shoo\n                constraint. If disabled, the values tuned in the Privacy module\n                tuning will be used.\n                ', tunable=Tunable(description='\n                    The radius of the constraint a Shooed Sim will attempt to\n                    route to.\n                    ', tunable_type=float, default=2.5), disabled_name=self.OPTIONAL_TUNABLE_DISABLED_NAME), unavailable_tooltip=OptionalTunable(description='\n                If enabled, allows a custom tool tip to be displayed when the\n                player tries to run an interaction on an object inside the\n                privacy region. If disabled, the values tuned in the Privacy\n                module tuning will be used.\n                ', tunable=TunableLocalizedStringFactory(description='\n                    Tool tip displayed when an object is not accessible due to\n                    being inside a privacy region.\n                    '), disabled_name=self.OPTIONAL_TUNABLE_DISABLED_NAME), embarrassed_affordance=OptionalTunable(description='\n                If enabled, a specific affordance can be tuned for a Sim to\n                play when walking into the privacy region. If disabled, the\n                values tuned in the Privacy module tuning will be used.\n                ', tunable=TunableReference(description='\n                    The affordance a Sim will play when getting embarrassed by\n                    walking in on a privacy situation.\n                    ', manager=services.get_instance_manager(Types.INTERACTION)), disabled_name=self.OPTIONAL_TUNABLE_DISABLED_NAME), post_route_affordance=OptionalTunable(description='\n                Optionally define an interaction that will run after the Sim\n                routes away.\n                ', tunable=TunableReference(description='\n                    The affordance a Sim will play when getting embarrassed by\n                    walking in on a privacy situation.\n                    ', manager=services.get_instance_manager(Types.INTERACTION))), privacy_cost_override=OptionalTunable(description='\n                If set, override the cost of the privacy region.\n                ', tunable=TunableRange(tunable_type=int, default=20, minimum=1), disabled_name=self.OPTIONAL_TUNABLE_DISABLED_NAME), additional_exit_offsets=TunableList(description="\n                If set, adds additional exit goals to add to the satisfy shoo\n                constraint.  For most cases this isn't needed, since most\n                privacy situations may kick a player out of a room through\n                a door and there are few exit options. \n                However for open-space privacy areas, default behavior\n                (using zone's corners) can cause a Sim to always attempt to exit \n                the privacy area in a consistent and often not optimal route, \n                (e.g. an open cross-shaped hall with 4 ways out, with default\n                behavior the Sim could consistently choose to exit using \n                the same route even though other routes would yield \n                a shorter distance out of the privacy region)\n                ", tunable=TunableVector2(default=TunableVector2.DEFAULT_ZERO)), reserved_surface_space=OptionalTunable(description='\n                If enabled privacy will generate an additional footprint around\n                the target object surface (if  routing_surface_only is enabled\n                then this will happen on the object routable surface). \n                This footprint will affect any Sim from routing through for the \n                duration of the interaction.\n                ', tunable=TunableTuple(description='\n                    Reserved space and blocking options for the created\n                    footprint.\n                    ', allow_routing=Tunable(description='\n                        If True, then the footprint will only discourage \n                        routing, instead of blocking the whole area from\n                        being used.\n                        ', tunable_type=bool, default=True), reserved_space=TunableReservedSpace(description='\n                        Defined space to generate the Jig that will block the \n                        routable surface space..\n                        ')), enabled_name='define_blocking_area'), vehicle_tests=OptionalTunable(description='\n                If enabled, vehicles that pass through this privacy region will\n                be tested to see if the vehicle is allowed in the privacy\n                region. Otherwise, the vehicle will always be affected by\n                privacy.\n                Note: The Object Routing Component specifies what happens when\n                the drone enters a privacy region.\n                ', tunable=TunableTestSet(description='\n                    The tests that the vehicle must pass to be allowed in the\n                    privacy region. \n                    Note: The Object Routing Component specifies what happens\n                    when the drone enters a privacy region.\n                    ')), privacy_violators=TunableEnumFlags(description='\n                Defines violators of privacy: currently, only SIM and VEHICLES are suppported.\n                for example: if  PrivacyViolators.SIM is false , but PrivacyViolators.VEHICLES is enabled\n                sims need not obey privacy rules for the object at hand.\n                ', enum_type=PrivacyViolators, allow_no_flags=True, default=PrivacyViolators.SIM | PrivacyViolators.VEHICLES), verify_tunable_callback=TunablePrivacy.verify_tunable_callback, description=description, **kwargs)
class CanvasComponent(Component,
                      HasTunableFactory,
                      AutoFactoryInit,
                      component_name=objects.components.types.CANVAS_COMPONENT,
                      persistence_key=persistence_protocols.PersistenceMaster.
                      PersistableData.CanvasComponent):
    FACTORY_TUNABLES = {
        'canvas_types':
        TunableEnumFlags(
            CanvasType,
            CanvasType.NONE,
            description=
            "\n            A painting texture must support at least one of these canvas types\n            to be applied to this object's painting area.\n            "
        )
    }

    def __init__(self, owner, *args, **kwargs):
        super().__init__(owner, *args, **kwargs)
        self.time_stamp = None
        self._painting_state = None

    def save_additional_data(self, canvas_data):
        pass

    def load_additional_data(self, canvas_data):
        pass

    def save(self, persistence_master_message):
        if self.painting_state is not None:
            persistable_data = persistence_protocols.PersistenceMaster.PersistableData(
            )
            persistable_data.type = persistence_protocols.PersistenceMaster.PersistableData.CanvasComponent
            canvas_data = persistable_data.Extensions[
                persistence_protocols.PersistableCanvasComponent.
                persistable_data]
            canvas_data.texture_id = self.painting_state.texture_id
            canvas_data.reveal_level = self.painting_state.reveal_level
            canvas_data.effect = self.painting_state.effect
            if self.painting_state.stage_texture_id is not None:
                canvas_data.stage_texture_id = self.painting_state.stage_texture_id
            if self.painting_state.overlay_texture_id is not None:
                canvas_data.overlay_texture_id = self.painting_state.overlay_texture_id
            if self.painting_state.reveal_texture_id is not None:
                canvas_data.reveal_texture_id = self.painting_state.reveal_texture_id
            if self.time_stamp:
                canvas_data.time_stamp = self.time_stamp
            self.save_additional_data(canvas_data)
            persistence_master_message.data.extend([persistable_data])

    def load(self, persistable_data):
        canvas_data = persistable_data.Extensions[
            persistence_protocols.PersistableCanvasComponent.persistable_data]
        if canvas_data.time_stamp:
            self.time_stamp = canvas_data.time_stamp
        if canvas_data.HasField('stage_texture_id'):
            stage_texture_id = canvas_data.stage_texture_id
        else:
            stage_texture_id = None
        if canvas_data.HasField('overlay_texture_id'):
            overlay_texture_id = canvas_data.overlay_texture_id
        else:
            overlay_texture_id = None
        if canvas_data.HasField('reveal_texture_id'):
            reveal_texture_id = canvas_data.reveal_texture_id
        else:
            reveal_texture_id = None
        self.load_additional_data(canvas_data)
        self.painting_state = PaintingState(canvas_data.texture_id,
                                            canvas_data.reveal_level, False,
                                            canvas_data.effect,
                                            stage_texture_id,
                                            overlay_texture_id,
                                            reveal_texture_id)

    @distributor.fields.ComponentField(op=distributor.ops.SetPaintingState,
                                       default=None)
    def painting_state(self) -> PaintingState:
        return self._painting_state

    @painting_state.setter
    def painting_state(self, value: PaintingState):
        self._painting_state = value

    @property
    def painting_reveal_level(self) -> int:
        if self.painting_state is not None:
            return self.painting_state.reveal_level

    @painting_reveal_level.setter
    def painting_reveal_level(self, reveal_level: int):
        if self.painting_state is not None:
            self.painting_state = self.painting_state.get_at_level(
                reveal_level)

    @property
    def painting_effect(self) -> int:
        if self.painting_state is not None:
            return self.painting_state.effect

    @painting_effect.setter
    def painting_effect(self, effect: int):
        if self.painting_state is not None:
            self.painting_state = self.painting_state.get_with_effect(effect)

    def set_painting_texture_id(self, texture_id):
        if self.painting_state is not None:
            self.painting_state = self.painting_state.set_painting_texture_id(
                texture_id)
        else:
            logger.error(
                'Object {} has no painting state and its trying to set a custom texture',
                self.owner,
                owner='camilogarcia')

    @componentmethod_with_fallback(lambda msg: None)
    def populate_icon_canvas_texture_info(self, msg):
        if self.painting_state is not None:
            if msg is not None:
                msg.texture_id = self.painting_state.texture_id
                msg.texture_effect = self.painting_state.effect

    @componentmethod_with_fallback(lambda *_, **__: None)
    def get_canvas_texture_id(self):
        if self.painting_state is not None:
            return self.painting_state.texture_id

    @componentmethod_with_fallback(lambda *_, **__: None)
    def get_canvas_texture_effect(self):
        return self.painting_effect

    def set_composite_image(self, resource_key, resource_key_type,
                            resource_key_group, *args, **kwargs):
        res_key = sims4.resources.Key(resource_key_type, resource_key,
                                      resource_key_group)
        painting_state = PaintingState.from_key(res_key,
                                                PaintingState.REVEAL_LEVEL_MAX,
                                                False, 0)
        self.painting_state = painting_state
Exemple #30
0
class CASTuning:
    CAS_BRANDED_TAG_DATA = TunableList(
        description=
        '\n        A list of CAS tag to data used to show a branded logo on the item\n        ',
        tunable=TunableTuple(
            description=
            '\n            Tuning for branded logo to use.\n            ',
            tag=TunableTag(
                description=
                '\n                Tag to use for the brand to be displayed\n                '
            ),
            icon=TunableIcon(
                description=
                '\n                Icon to be displayed on the item\n                '
            ),
            background_type=TunableEnumEntry(
                description=
                '\n                Background to be used for it\n                ',
                tunable_type=CASBrandedLogoBackground,
                default=CASBrandedLogoBackground.LIGHT),
            export_class_name='CasBrandedTagEntry'),
        export_modes=ExportModes.ClientBinary)
    CAS_SPECIES_PAINT_POSES = TunableMapping(
        description=
        '\n        A mapping of species type to data that is required for the paint pose ui\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The species type that this entry applies to.\n            ',
            tunable_type=SpeciesExtended,
            default=SpeciesExtended.HUMAN,
            invalid_enums=(SpeciesExtended.INVALID, )),
        value_type=TunableList(
            description=
            '\n            A list of CasPaintPostTuples\n            ',
            tunable=TunableTuple(
                description=
                '\n                Data required for each UI Paint pose button.\n                ',
                icon=TunableIcon(
                    description=
                    '\n                    Icon to be displayed on the button for the pose\n                    ',
                    tuning_group=GroupNames.UI),
                icon_selected=TunableIcon(
                    description=
                    '\n                    Icon to be displayed on the button when the pose button is selected\n                    ',
                    tuning_group=GroupNames.UI),
                pose=TunableEnumEntry(
                    description=
                    '\n                    The pose to play when the button is pressed\n                    ',
                    tunable_type=CASPaintPose,
                    default=CASPaintPose.NONE),
                export_class_name='CasPaintPoseTuple')),
        export_modes=ExportModes.ClientBinary,
        tuple_name='CasPaintPoseKeyValue')
    CAS_VOICES_DATA = TunableMapping(
        description=
        '\n        A mapping of species type to data required for the personality panel ui.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The species type that this entry applies to.\n            ',
            tunable_type=SpeciesExtended,
            default=SpeciesExtended.HUMAN,
            invalid_enums=(SpeciesExtended.INVALID, )),
        value_type=TunableMapping(
            description=
            '\n            A mapping of age type to data required for displaying voices in the ui.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The age that this entry applies to.\n                ',
                tunable_type=Age,
                default=Age.ADULT),
            value_type=TunableList(
                description=
                '\n                a list of voice data for this species at this age.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    data required to display this voice in the ui.\n                    ',
                    icon=TunableIcon(
                        description=
                        '\n                        Icon to be displayed as voice button.\n                        ',
                        tuning_group=GroupNames.UI),
                    icon_selected=TunableIcon(
                        description=
                        '\n                        Icon to be displayed as voice button when it is selected.\n                        ',
                        tuning_group=GroupNames.UI),
                    tooltip=TunableLocalizedString(
                        description=
                        '\n                        Localized name of this voice.\n                        '
                    ),
                    export_class_name='CasVoicesDataTuple')),
            tuple_name='CasVoicesAgeKeyValue'),
        export_modes=ExportModes.ClientBinary,
        tuple_name='CasVoicesSpeciesKeyValue')
    CAS_RANDOMIZE_FILTERS = TunableMapping(
        description=
        '\n        An Ordered list of randomization menu items that will appear in the randomization panel ui in CAS. \n        The list is filtered by the criteria on each menu item.\n        ',
        key_type=Tunable(
            description=
            '\n            An integer value used to set the specific order of the menu items\n            in the ui. The lower numbers are displayed first in the ui.\n            ',
            tunable_type=int,
            default=0),
        value_type=TunableTuple(
            description=
            '\n            A randomization menu item and its inclusion (and/or exclusion) criteria.\n            ',
            criteria=CASContextCriterionList(
                description=
                '\n                Use this menu item if all of the specified criteria are met.\n                '
            ),
            flags=TunableList(
                description=
                '\n                A list of randomization flags for this item.\n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    A randomization flag.\n                    ',
                    tunable_type=CASRandomizeFlag,
                    default=CASRandomizeFlag.RANDOMIZE_BY_MENUSTATE)),
            name=TunableLocalizedString(
                description=
                '\n                The name of this menu item displayed in the ui.\n                '
            ),
            required_flags=TunableList(
                description=
                '\n                A list of randomization flags that are required to be enabled \n                in order for this menu item to be enabled. \n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    A randomization flag.\n                    ',
                    tunable_type=CASRandomizeFlag,
                    default=CASRandomizeFlag.RANDOMIZE_BY_MENUSTATE)),
            export_class_name='CasRandomizeItemTuple'),
        tuple_name='CasRandomizeItemsKeyValue',
        export_modes=(ExportModes.ClientBinary, ))
    CAS_COPY_FILTERS = TunableList(
        description=
        '\n        An Ordered list of copy menu items that will appear in the randomization panel ui in CAS. \n        The list is filtered by the criteria on each menu item.\n        ',
        tunable=TunableTuple(
            description=
            '\n            A copy menu item and its inclusion (and/or exclusion) criteria.\n            ',
            criteria=CASContextCriterionList(
                description=
                '\n                Use this menu item if all of the specified criteria are met.\n                '
            ),
            flags=TunableList(
                description=
                '\n                A list of copy flags for this item.\n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    A copy flag.\n                    ',
                    tunable_type=CASRandomizeFlag,
                    default=CASRandomizeFlag.RANDOMIZE_BY_MENUSTATE)),
            name=TunableLocalizedString(
                description=
                '\n                The name of this menu item displayed in the ui.\n                '
            ),
            required_flags=TunableList(
                description=
                '\n                A list of copy flags that are required to be enabled \n                in order for this menu item to be enabled. \n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    A copy flag.\n                    ',
                    tunable_type=CASRandomizeFlag,
                    default=CASRandomizeFlag.RANDOMIZE_BY_MENUSTATE)),
            export_class_name='CasCopyItemEntry'),
        export_modes=(ExportModes.ClientBinary, ))
    CAS_ADD_SIM_MENU_DATA = TunableMapping(
        description=
        '\n        An ordered mapping of menu data used for the Add Sim portion of CAS.\n        ',
        key_name='index',
        key_type=Tunable(
            description=
            '\n            The order in which these entries should be added. 1 is first, 2 is\n            second, etc.\n            ',
            tunable_type=int,
            default=0),
        value_name='data',
        value_type=TunableTuple(
            description=
            '\n            Data associated with an add Sim button in CAS.\n            ',
            criteria=CASContextCriterionList(
                description=
                '\n                Only add this menu item if the criteria are met.\n                '
            ),
            parent_index=Tunable(
                description=
                '\n                The index of the parent entry if this is a child to another\n                entry in the list. 0 if this entry has no parent.\n                ',
                tunable_type=int,
                default=0),
            tooltip=TunableLocalizedString(
                description=
                '\n                The tooltip when hovering over this entry.\n                ',
                allow_none=True),
            icon=TunableResourceKey(
                description=
                '\n                The icon for this entry.\n                ',
                allow_none=True,
                pack_safe=True),
            icon_selected=TunableResourceKey(
                description=
                '\n                The icon when this entry is selected.\n                ',
                allow_none=True,
                pack_safe=True),
            audio_name=Tunable(
                description=
                '\n                The audio to play when this entry is selected.\n                ',
                tunable_type=str,
                default='',
                allow_empty=True),
            flair_name=Tunable(
                description=
                '\n                Flair to apply to this entry (for instance, god rays).\n                ',
                tunable_type=str,
                default='',
                allow_empty=True),
            tutorial_control_enum=TunableEnumEntry(
                description=
                '\n                The enum used for tutorial controls. UI_INVALID should be\n                used if this entry has no tutorial control.\n                ',
                tunable_type=TutorialTipUiElement,
                default=TutorialTipUiElement.UI_INVALID),
            action=TunableEnumEntry(
                description=
                '\n                The action to take when clicking this entry.\n                ',
                tunable_type=CASAddSimAction,
                default=CASAddSimAction.ACTION_NONE),
            species=TunableEnumEntry(
                description=
                "\n                The species for this entry. Species.INVALID indicates no\n                preference or it's not relevant to this menu entry.\n                ",
                tunable_type=SpeciesExtended,
                default=SpeciesExtended.INVALID),
            occult_type=TunableEnumFlags(
                description=
                '\n                The occult type for this entry, if any.\n                ',
                enum_type=OccultType,
                allow_no_flags=True),
            limit_genetics_species=TunableEnumSet(
                description=
                '\n                Species in this list will only be allowed through if the action\n                for this entry is GENETICS. This is very likely only going to be\n                used for pet genetics.\n                ',
                enum_type=SpeciesExtended,
                enum_default=SpeciesExtended.INVALID,
                allow_empty_set=True),
            export_class_name='CasAddSimMenuData'),
        tuple_name='CasAddSimMenuDataKeyValue',
        export_modes=(ExportModes.ClientBinary, ))