class RelationshipBitTest(HasTunableSingletonFactory, AutoFactoryInit,
                          BaseTest):
    FACTORY_TUNABLES = {
        'subject':
        TunableEnumFlags(
            description=
            '\n            Owner(s) of the relationship(s) to be compared with subject_b.\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.Actor),
        'target':
        TunableEnumFlags(
            description=
            '\n            Owner(s) of the relationship(s) to be compared with subject_a.\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.TargetSim),
        'relationship_bits':
        TunableSet(
            description=
            '\n            Any of these relationship bits will pass the test.\n            ',
            tunable=TunableReference(services.relationship_bit_manager()),
            minlength=1),
        'test_event':
        TunableVariant(
            description='\n            Event to listen to.\n            ',
            locked_args={
                'Bit Added': TestEvent.AddRelationshipBit,
                'Bit Removed': TestEvent.RemoveRelationshipBit
            },
            default='Bit Added')
    }

    @property
    def test_events(self):
        return (self.test_event, )

    def get_expected_args(self):
        return {
            'subject': self.subject,
            'target': self.target,
            'relationship_bit': event_testing.test_constants.FROM_EVENT_DATA
        }

    @cached_test
    def __call__(self, subject, target, relationship_bit):
        if relationship_bit not in self.relationship_bits:
            return TestResult(
                False,
                'Event {} did not trigger for bit {} between Sims {} and {}, bits of interest: {}',
                relationship_bit, subject, target, self.relationship_bits)
        return TestResult.TRUE
Beispiel #2
0
class WalkstyleBehaviorOverride(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'walkstyle_behavior_priority':
        TunableEnumEntry(
            description=
            '\n            Define the priority of this override relative to other overrides.\n            This is meaningful for the non-additive properties of this override.\n            ',
            tunable_type=WalkstyleBehaviorOverridePriority,
            default=WalkstyleBehaviorOverridePriority.DEFAULT),
        'run_required_total_distance':
        _get_tunable_override(tunable=TunableRange(
            description=
            '\n                For an entire route, the minimum distance required for Sim to\n                run.\n                ',
            tunable_type=float,
            minimum=0,
            default=20)),
        'run_required_segment_distance':
        _get_tunable_override(tunable=TunableRange(
            description=
            '\n                For a specific route segment, the minimum distance required for\n                the Sim to run.\n                ',
            tunable_type=float,
            minimum=0,
            default=10)),
        'run_walkstyle':
        _get_tunable_override(tunable=TunableWalkstyle(
            description=
            '\n                Override the walkstyle to use when this Sim is supposed to be running.\n                '
        )),
        'short_walkstyle':
        _get_tunable_override(tunable=TunableWalkstyle(
            description=
            '\n                Override walkstyle used when Sims are routing over a short distance.\n                '
        )),
        'wading_walkstyle':
        _get_tunable_override(tunable=TunableWalkstyle(
            description=
            '\n                Override walkstyle used when Sims are walking through the water.\n                '
        )),
        'additional_run_flags':
        TunableEnumFlags(
            description=
            '\n            Define where the Sim is allowed to run.\n            \n            NOTE: This is *additive*, meaning the resulting run flags for a Sim\n            are defined by the default tuning plus all overrides. However,\n            removed_run_flags is applied last.\n            ',
            enum_type=WalkStyleRunAllowedFlags,
            allow_no_flags=True),
        'removed_run_flags':
        TunableEnumFlags(
            description=
            '\n            Modify a Sim\'s ability to run.\n            \n            NOTE: This is *additive* with respect to all overrides, and is\n            applied to the combination of the default tuning plus\n            additional_run_flags from all overrides.\n            \n            e.g.: The default behavior for a human is to exclusively run\n            outdoors. A hypothetical "excitable" trait might add the ability to\n            run indoors (meaning the total result is indoors+outdoors). However,\n            the "pregnant" trait might remove the ability to run (both outdoors\n            and indoors), effectively preventing the Sim from running at all.\n            ',
            enum_type=WalkStyleRunAllowedFlags,
            allow_no_flags=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)
Beispiel #4
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)
Beispiel #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
Beispiel #6
0
class PaintingTexture(metaclass=TunedInstanceMetaclass,
                      manager=services.get_instance_manager(
                          sims4.resources.Types.RECIPE)):
    INSTANCE_TUNABLES = {
        'texture':
        TunableResourceKey(None,
                           resource_types=[sims4.resources.Types.TGA],
                           allow_none=True),
        '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
Beispiel #7
0
 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)
class PackSpecificTuning:
    VENUE_PACK_TUNING = TunableMapping(
        description=
        "\n        Venue tuning that is needed by UI when that venue's pack is not installed.\n        ",
        key_name='venue_id',
        key_type=TunableReference(
            description=
            '\n            Reference to the venue that this data represents\n            ',
            manager=services.get_instance_manager(sims4.resources.Types.VENUE),
            pack_safe=True),
        value_name='data',
        value_type=TunableTuple(
            description=
            "\n            Venue data that is shown in the UI when this venue's pack is not installed.\n            ",
            gallery_download_venue=OptionalTunable(
                description=
                '\n                If tuned, the tuned venue tuning will be substituted if this\n                venue is downloaded from the gallery by a player who is not\n                entitled to it. The default behavior is to substitute the\n                generic venue. This tuned venue will also determine download\n                compatibility (for instance, only residential venues can be \n                downloaded to owned residential venues).\n                ',
                tunable=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.VENUE))),
            venue_name=TunableLocalizedStringFactory(
                description=
                '\n                Name that will be displayed for the venue when the pack containing \n                that venue is not installed\n                '
            ),
            venue_flags=TunableEnumFlags(
                description=
                '\n                Venue flags used to mark a venue with specific properties.\n                ',
                enum_type=VenueFlags,
                allow_no_flags=True,
                default=VenueFlags.NONE),
            export_class_name='VenueDataTuple'),
        tuple_name='VenuePackTuning',
        export_modes=ExportModes.All,
        verify_tunable_callback=verify_venue_tuning)
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
Beispiel #10
0
class DaycareLiability(Liability, HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'participants': TunableEnumFlags(description='\n            The participants this liability is applied to.\n            ', enum_type=ParticipantTypeSim, default=ParticipantTypeSim.Actor)}

    def __init__(self, interaction, *args, participants=DEFAULT, **kwargs):
        if participants is DEFAULT:
            participants = ParticipantTypeSim.Actor
        super().__init__(*args, participants=participants, **kwargs)
        self._sim_infos = []

    def on_add(self, interaction):
        super().on_add(interaction)
        participants = interaction.get_participants(self.participants)
        for participant in participants:
            self._sim_infos.append(participant.sim_info)

    def on_run(self):
        super().on_run()
        daycare_service = services.daycare_service()
        for sim_info in self._sim_infos:
            if daycare_service is not None:
                daycare_service.set_sim_globally_unavailable(sim_info)
                daycare_service.set_sim_unavailable(sim_info)

    def release(self):
        super().release()
        daycare_service = services.daycare_service()
        for sim_info in self._sim_infos:
            if daycare_service is not None:
                daycare_service.set_sim_globally_available(sim_info)
                daycare_service.set_sim_available(sim_info)
Beispiel #11
0
 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)
Beispiel #12
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
 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)
     }
Beispiel #14
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)
Beispiel #15
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)
Beispiel #18
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)
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
Beispiel #20
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
Beispiel #21
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)
Beispiel #24
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
Beispiel #30
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)