class RelationshipBitCollection(metaclass=HashedTunedInstanceMetaclass,
                                manager=services.relationship_bit_manager()):
    __qualname__ = 'RelationshipBitCollection'
    INSTANCE_TUNABLES = {
        'name':
        TunableLocalizedString(
            export_modes=ExportModes.All,
            description='Name to be displayed for the collection.'),
        'icon':
        TunableResourceKey(
            'PNG:missing_image',
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All,
            description='Icon to be displayed for the collection.'),
        'collection_id':
        TunableEnumEntry(RelationshipBitCollectionUid,
                         RelationshipBitCollectionUid.Invalid,
                         export_modes=ExportModes.All,
                         description='The unique id of the relationship bit')
    }
    is_rel_bit = False

    @classmethod
    def _verify_tuning_callback(cls):
        validate_locked_enum_id(RelationshipBitCollection, cls.collection_id,
                                cls, RelationshipBitCollectionUid.Invalid)

    @classmethod
    def matches_bit(cls, bit_type):
        return cls.collection_id in bit_type.collection_ids
Exemplo n.º 2
0
class FamilyAspirationTriggerTest(event_testing.test_base.BaseTest):
    __qualname__ = 'FamilyAspirationTriggerTest'
    test_events = (event_testing.test_events.TestEvent.FamilyTrigger, )
    USES_EVENT_DATA = True
    FACTORY_TUNABLES = {
        'description':
        '\n            This is a special test used to receive the completion of a Familial\n            Aspiration. To properly use this test, one would create a Familial\n            Aspiration with an objective test on it, and tune the family\n            members who would care to receive it. Then create a new Aspiration\n            for the family members to receive it, and use this test to tune the\n            Familial Aspiration you created as the sender.\n        ',
        'aspiration_trigger':
        TunableReference(
            description=
            '\n            If this aspiration is completed because a family member completed\n            the corresponding trigger, the test will pass.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ASPIRATION),
            class_restrictions='AspirationFamilialTrigger'),
        'target_family_relationships':
        TunableSet(
            description=
            '\n            These relationship bits will get an event message upon Aspiration\n            completion that they can test for.\n            ',
            tunable=TunableReference(
                manager=services.relationship_bit_manager()))
    }

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

    def get_expected_args(self):
        return {
            'sim_infos': ParticipantType.Actor,
            'trigger': event_testing.test_events.FROM_EVENT_DATA
        }

    @cached_test
    def __call__(self, sim_infos=None, trigger=None):
        if trigger is None:
            if sim_infos is not None:
                for sim_info in sim_infos:
                    for relationship in sim_info.relationship_tracker:
                        for relationship_bit in self.target_family_relationships:
                            while relationship.has_bit(relationship_bit):
                                target_sim_info = relationship.find_target_sim_info(
                                )
                                if target_sim_info.aspiration_tracker.milestone_completed(
                                        self.aspiration_trigger.guid64):
                                    return TestResult.TRUE
            return TestResult(
                False,
                'FamilyAspirationTriggerTest: No valid sims with the aspiration found.'
            )
        if self.aspiration_trigger.guid64 == trigger.guid64:
            return TestResult.TRUE
        return TestResult(
            False,
            'FamilyAspirationTriggerTest: Tuned trigger {} does not match event trigger {}.',
            self.aspiration_trigger, trigger)
Exemplo n.º 3
0
class CheatWoohooTuning:
    CHEAT_WOOHOO_BITS = TunableList(
        TunableReference(manager=services.relationship_bit_manager()))
    CHEAT_WOOHOO_TRACK = TunableReference(
        manager=services.statistic_manager(),
        class_restrictions=('RelationshipTrack', ))
    CHEAT_WOOHOO_COMMODITY = TunableReference(
        manager=services.statistic_manager(),
        class_restrictions=('Commodity', ))
    CHEAT_WOOHOO_BUFF = TunableReference(manager=services.buff_manager())
    CHEAT_WOOHOO_SOCIALCONTEXT = TunableReference(
        manager=services.statistic_manager(),
        class_restrictions='RelationshipTrack')
Exemplo n.º 4
0
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
Exemplo n.º 5
0
class RelationshipBitCollection(metaclass=HashedTunedInstanceMetaclass,
                                manager=services.relationship_bit_manager()):
    INSTANCE_TUNABLES = {
        'name':
        TunableLocalizedString(
            description=
            '\n            Name to be displayed for the collection.\n            ',
            export_modes=ExportModes.All),
        'icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed for the collection.\n            ',
            allow_none=True,
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'collection_id':
        TunableEnumEntry(
            description=
            '\n            The unique id of the relationship bit\n            ',
            tunable_type=RelationshipBitCollectionUid,
            default=RelationshipBitCollectionUid.Invalid,
            export_modes=ExportModes.All)
    }

    @classproperty
    def is_collection(cls):
        return True

    @classmethod
    def _verify_tuning_callback(cls):
        validate_locked_enum_id(RelationshipBitCollection, cls.collection_id,
                                cls, RelationshipBitCollectionUid.Invalid)

    @classmethod
    def matches_bit(cls, bit_type):
        return cls.collection_id in bit_type.collection_ids
Exemplo n.º 6
0
class TotalRelationshipBitTest(event_testing.test_base.BaseTest):
    __qualname__ = 'TotalRelationshipBitTest'
    test_events = (TestEvent.AddRelationshipBit, )
    USES_DATA_OBJECT = True
    FACTORY_TUNABLES = {
        'description':
        'Gate availability by a relationship status.',
        'use_current_relationships':
        Tunable(
            bool,
            False,
            description=
            'Use the current number of relationships held at this bit rather than the total number ever had.'
        ),
        'relationship_bits':
        TunableSet(
            TunableReference(
                services.relationship_bit_manager(),
                description='The relationship bit that will be checked.',
                class_restrictions='RelationshipBit')),
        'num_relations':
        TunableThreshold(
            description=
            'Number of Sims with specified relationships required to pass.')
    }

    def __init__(self, use_current_relationships, relationship_bits,
                 num_relations, **kwargs):
        super().__init__(**kwargs)
        self.use_current_relationships = use_current_relationships
        self.relationship_bits = relationship_bits
        self.num_relations = num_relations

    def get_expected_args(self):
        return {
            'data_object': event_testing.test_events.FROM_DATA_OBJECT,
            'objective_guid64': event_testing.test_events.OBJECTIVE_GUID64
        }

    @cached_test
    def __call__(self, data_object=None, objective_guid64=None):
        current_relationships = 0
        for relationship_bit in self.relationship_bits:
            if self.use_current_relationships:
                current_relationships += data_object.get_current_total_relationships(
                    relationship_bit)
            else:
                current_relationships += data_object.get_total_relationships(
                    relationship_bit)
        relative_start_value = data_object.get_starting_values(
            objective_guid64)
        if relative_start_value is not None:
            relations = 0
            current_relationships -= relative_start_value[relations]
        if not self.num_relations.compare(current_relationships):
            return TestResultNumeric(
                False,
                'TotalRelationshipBitTest: Not enough relationships.',
                current_value=current_relationships,
                goal_value=self.num_relations.value,
                is_money=False)
        return TestResult.TRUE

    def save_relative_start_values(self, objective_guid64, data_object):
        current_relationships = 0
        for relationship_bit in self.relationship_bits:
            if self.use_current_relationships:
                current_relationships += data_object.get_current_total_relationships(
                    relationship_bit)
            else:
                current_relationships += data_object.get_total_relationships(
                    relationship_bit)
        data_object.set_starting_values(objective_guid64,
                                        [current_relationships])

    def tuning_is_valid(self):
        if self.relationship_bits:
            return True
        return False

    def goal_value(self):
        return self.num_relations.value
Exemplo n.º 7
0
class RelationshipBit(HasTunableReference,
                      SuperAffordanceProviderMixin,
                      MixerProviderMixin,
                      metaclass=HashedTunedInstanceMetaclass,
                      manager=services.relationship_bit_manager()):
    INSTANCE_TUNABLES = {
        'display_name':
        TunableLocalizedStringFactory(
            description=
            '\n            Localized name of this bit\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'bit_description':
        TunableLocalizedStringFactory(
            description=
            '\n            Localized description of this bit\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed for the relationship bit.\n            ',
            allow_none=True,
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'bit_added_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is added.\n            ',
            tunable=TunableTuple(
                notification=TunableUiDialogNotificationSnippet(),
                show_if_unselectable=Tunable(
                    description=
                    '\n                    If this is checked, then the notification is displayed if\n                    the owning Sim is not selectable, but the target is.\n                    Normally, notifications are only displayed if the owning Sim\n                    is selectable.\n                    ',
                    tunable_type=bool,
                    default=False))),
        'bit_removed_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is removed.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'depth':
        Tunable(
            description=
            '\n            The amount of depth provided by the bit.\n            ',
            tunable_type=int,
            default=0),
        'priority':
        Tunable(
            description=
            '\n            Priority of the bit.  This is used when a bit turns on while a\n            mutually exclusive bit is already on.\n            ',
            tunable_type=float,
            default=0),
        'display_priority':
        Tunable(
            description=
            '\n            The priority of this bit with regards to UI.  Only the highest\n            priority bits are displayed.\n            ',
            tunable_type=int,
            default=0,
            export_modes=ExportModes.All),
        'exclusive':
        Tunable(
            description=
            "\n            Whether or not the bit is exclusive. This means that a sim can only have \n            this bit with one other sim.  If you attempt to add an exclusive bit to \n            a sim that already has the same one with another sim, it will remove the \n            old bit.\n            \n            Example: A sim can only be BFF's with one other sim.  If the sim asks \n            another sim to be their BFF, the old bit is removed.\n            ",
            tunable_type=bool,
            default=False),
        'visible':
        Tunable(
            description=
            "\n            If True, this bit has the potential to be visible when applied,\n            depending on display_priority and the other active bits.  If False,\n            the bit will not be displayed unless it's part of the\n            REL_INSPECTOR_TRACK bit track.\n            ",
            tunable_type=bool,
            default=True),
        'group_id':
        TunableEnumEntry(
            description=
            '\n            The group this bit belongs to.  Two bits of the same group cannot\n            belong in the same set of bits for a given relationship.\n            ',
            tunable_type=RelationshipBitType,
            default=RelationshipBitType.NoGroup),
        'triggered_track':
        TunableReference(
            description=
            '\n            If set, the track that is triggered when this bit is set\n            ',
            manager=services.statistic_manager(),
            allow_none=True,
            class_restrictions='RelationshipTrack'),
        'required_bits':
        TunableList(
            description=
            '\n            List of all bits that are required to be on in order to allow this\n            bit to turn on.\n            ',
            tunable=TunableReference(services.relationship_bit_manager())),
        'timeout':
        TunableSimMinute(
            description=
            '\n            The length of time this bit will last in sim minutes.  0 means the\n            bit will never timeout.\n            ',
            default=0),
        'remove_on_threshold':
        OptionalTunable(tunable=TunableTuple(
            description=
            '\n                If enabled, this bit will be removed when the referenced track\n                reaches the appropriate threshold.\n                ',
            track=TunableReference(
                description=
                '\n                    The track to be tested.\n                    ',
                manager=services.statistic_manager(),
                class_restrictions='RelationshipTrack'),
            threshold=TunableThreshold(
                description=
                '\n                    The threshold at which to remove this bit.\n                    '
            ))),
        'historical_bits':
        OptionalTunable(tunable=TunableList(tunable=TunableTuple(
            age_trans_from=
            TunableEnumEntry(description=
                             '\n                        Age we are transitioning out of.\n                        ',
                             tunable_type=sims.sim_info_types.Age,
                             default=sims.sim_info_types.Age.CHILD),
            new_historical_bit=TunableReference(
                description=
                '\n                        New historical bit the sim obtains\n                        ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.RELATIONSHIP_BIT))))),
        'collection_ids':
        TunableList(tunable=TunableEnumEntry(
            description=
            '\n                The bit collection id this bit belongs to, like family,\n                friends, romance. Default to be All.\n                ',
            tunable_type=RelationshipBitCollectionUid,
            default=RelationshipBitCollectionUid.All,
            export_modes=ExportModes.All)),
        'buffs_on_add_bit':
        TunableList(tunable=TunableTuple(
            buff_ref=buffs.tunable.
            TunableBuffReference(description=
                                 '\n                    Buff that gets added to sim when bit is added.\n                    '
                                 ),
            amount=Tunable(
                description=
                '\n                    If buff is tied to commodity the amount to add to the\n                    commodity.\n                    ',
                tunable_type=float,
                default=1),
            only_add_once=Tunable(
                description=
                '\n                    If True, the buff should only get added once no matter how\n                    many times this bit is being applied.\n                    ',
                tunable_type=bool,
                default=False))),
        'buffs_to_add_if_on_active_lot':
        TunableList(
            description=
            "\n            List of buffs to add when a sim that I share this relationship with\n            is in the household that owns the lot that I'm on.\n            ",
            tunable=buffs.tunable.TunableBuffReference(
                description=
                '\n                Buff that gets added to sim when bit is added.\n                '
            )),
        'autonomy_multiplier':
        Tunable(
            description=
            '\n            This value is multiplied to the autonomy score of any interaction\n            performed between the two Sims.  For example, when the Sim decides\n            to socialize, she will start looking at targets to socialize with.\n            If there is a Sim who she shares this bit with, her final score for\n            socializing with that Sim will be multiplied by this value.\n            ',
            tunable_type=float,
            default=1),
        'relationship_culling_prevention':
        TunableEnumEntry(
            description=
            '\n            Determine if bit should prevent relationship culling.  \n            \n            ALLOW_ALL = all culling\n            PLAYED_ONLY = only cull if not a played household\n            PLAYED_AND_UNPLAYED = disallow culling for played and unplayed sims. (e.g. family bits)\n            ',
            tunable_type=RelationshipBitCullingPrevention,
            default=RelationshipBitCullingPrevention.ALLOW_ALL),
        'persisted_tuning':
        Tunable(
            description=
            '\n            Whether this bit will persist when saving a Sim. \n            \n            For example, a Sims is good_friends should be set to true, but\n            romantic_gettingMarried should not be saved.\n            ',
            tunable_type=bool,
            default=True),
        'bit_added_loot_list':
        TunableList(
            description=
            '\n            A list of loot operations to apply when this relationship bit is\n            added.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
                                     class_restrictions=('LootActions', ),
                                     pack_safe=True)),
        'directionality':
        TunableEnumEntry(
            description=
            '\n            The direction that this Relationship bit points.  Bidirectional\n            means that both Sims will be given this bit if it is added.\n            Unidirectional means that only one Sim will be given this bit.\n            If it is coming from loot that bit will be given to the Actor.\n            ',
            tunable_type=RelationshipDirection,
            default=RelationshipDirection.BIDIRECTIONAL)
    }
    is_track_bit = False
    trait_replacement_bits = None
    _cached_commodity_flags = None

    def __init__(self):
        self._buff_handles = None
        self._conditional_removal_listener = None
        self._appropriate_buffs_handles = None

    @classproperty
    def persisted(cls):
        return cls.persisted_tuning

    @classproperty
    def is_collection(cls):
        return False

    def add_buffs_for_bit_add(self, sim, relationship, from_load):
        for buff_data in self.buffs_on_add_bit:
            buff_type = buff_data.buff_ref.buff_type
            if from_load and buff_type.commodity:
                continue
            if buff_data.only_add_once:
                if buff_type.guid64 in relationship.get_bit_added_buffs(
                        sim.sim_id):
                    continue
                relationship.add_bit_added_buffs(sim.sim_id, buff_type)
            if buff_type.commodity:
                tracker = sim.get_tracker(buff_type.commodity)
                tracker.add_value(buff_type.commodity, buff_data.amount)
                sim.set_buff_reason(buff_type, buff_data.buff_ref.buff_reason)
            else:
                buff_handle = sim.add_buff(
                    buff_type, buff_reason=buff_data.buff_ref.buff_reason)
                if self._buff_handles is None:
                    self._buff_handles = []
                self._buff_handles.append((sim.sim_id, buff_handle))

    def _apply_bit_added_loot(self, sim_info, target_sim_info):
        resolver = DoubleSimResolver(sim_info, target_sim_info)
        for loot in self.bit_added_loot_list:
            loot.apply_to_resolver(resolver)

    def on_add_to_relationship(self, sim, target_sim_info, relationship,
                               from_load):
        if relationship._is_object_rel:
            return
        target_sim = target_sim_info.get_sim_instance()
        self.add_buffs_for_bit_add(sim, relationship, from_load)
        if target_sim is not None and self.directionality == RelationshipDirection.BIDIRECTIONAL:
            self.add_buffs_for_bit_add(target_sim, relationship, from_load)
        if not from_load:
            self._apply_bit_added_loot(sim.sim_info, target_sim_info)
            if self.directionality == RelationshipDirection.BIDIRECTIONAL:
                self._apply_bit_added_loot(target_sim_info, sim.sim_info)

    def on_remove_from_relationship(self, sim, target_sim_info):
        target_sim = target_sim_info.get_sim_instance()
        if self._buff_handles is not None:
            for (sim_id, buff_handle) in self._buff_handles:
                if sim.sim_id == sim_id:
                    sim.remove_buff(buff_handle)
                elif target_sim is not None:
                    target_sim.remove_buff(buff_handle)
            self._buff_handles = None

    def add_appropriateness_buffs(self, sim_info):
        if not self._appropriate_buffs_handles:
            if self.buffs_to_add_if_on_active_lot:
                self._appropriate_buffs_handles = []
                for buff in self.buffs_to_add_if_on_active_lot:
                    handle = sim_info.add_buff(buff.buff_type,
                                               buff_reason=buff.buff_reason)
                    self._appropriate_buffs_handles.append(handle)

    def remove_appropriateness_buffs(self, sim_info):
        if self._appropriate_buffs_handles is not None:
            for buff in self._appropriate_buffs_handles:
                sim_info.remove_buff(buff)
            self._appropriate_buffs_handles = None

    def add_conditional_removal_listener(self, listener):
        if self._conditional_removal_listener is not None:
            logger.error(
                'Attempting to add a conditional removal listener when one already exists; old one will be overwritten.',
                owner='jjacobson')
        self._conditional_removal_listener = listener

    def remove_conditional_removal_listener(self):
        listener = self._conditional_removal_listener
        self._conditional_removal_listener = None
        return listener

    def __repr__(self):
        bit_type = type(self)
        return '<({}) Type: {}.{}>'.format(bit_type.__name__,
                                           bit_type.__mro__[1].__module__,
                                           bit_type.__mro__[1].__name__)

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.historical_bits is not None:
            for bit in cls.historical_bits:
                pass

    @classmethod
    def commodity_flags(cls):
        if cls._cached_commodity_flags is None:
            commodity_flags = set()
            for super_affordance in cls.get_provided_super_affordances_gen():
                commodity_flags.update(super_affordance.commodity_flags)
            cls._cached_commodity_flags = frozenset(commodity_flags)
        return cls._cached_commodity_flags

    def show_bit_added_dialog(self, owner, sim, target_sim_info):
        dialog = self.bit_added_notification.notification(
            owner, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    def show_bit_removed_dialog(self, sim, target_sim_info):
        dialog = self.bit_removed_notification(
            sim, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    @classmethod
    def matches_bit(cls, bit_type):
        return cls is bit_type
Exemplo n.º 8
0
class BaseRelationshipTest(BaseTest):
    UNIQUE_TARGET_TRACKING_AVAILABLE = True
    MIN_RELATIONSHIP_VALUE = -100.0
    MAX_RELATIONSHIP_VALUE = 100.0

    @TunableFactory.factory_option
    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)
        }

    @staticmethod
    def _verify_tunable_callback(instance_class, tunable_name, source, value):
        overlapping_bits = (value.required_relationship_bits.match_any
                            | value.required_relationship_bits.match_all) & (
                                value.prohibited_relationship_bits.match_any
                                | value.prohibited_relationship_bits.match_all)
        if overlapping_bits:
            logger.error(
                'Tuning error in {}. Cannot have overlapping required and prohibited relationship bits: {}'
                .format(instance_class, overlapping_bits))

    FACTORY_TUNABLES = {
        'subject':
        TunableEnumFlags(
            description=
            '\n            Owner(s) of the relationship(s).\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.Actor),
        'required_relationship_bits':
        TunableTuple(
            match_any=TunableSet(
                description=
                '\n                Any of these relationship bits will pass the test.\n                ',
                tunable=TunableReference(services.relationship_bit_manager(),
                                         pack_safe=True)),
            match_all=TunableSet(
                description=
                '\n                All of these relationship bits must be present to pass the\n                test.\n                ',
                tunable=TunablePackSafeReference(
                    services.relationship_bit_manager()),
                allow_none=True)),
        'prohibited_relationship_bits':
        TunableTuple(
            match_any=TunableSet(
                description=
                '\n                If any of these relationship bits match the test will fail.\n                ',
                tunable=TunableReference(services.relationship_bit_manager(),
                                         pack_safe=True)),
            match_all
            =TunableSet(
                description=
                '\n                All of these relationship bits must match to fail the test.\n                ',
                tunable=TunableReference(
                    services.relationship_bit_manager()))),
        'relationship_score_interval':
        TunableInterval(
            description=
            '\n            The range that the relationship score must be within in order for\n            this test to pass.\n            ',
            tunable_type=float,
            default_lower=MIN_RELATIONSHIP_VALUE,
            default_upper=MAX_RELATIONSHIP_VALUE,
            minimum=MIN_RELATIONSHIP_VALUE,
            maximum=MAX_RELATIONSHIP_VALUE),
        'test_event':
        TunableEnumEntry(
            description=
            '\n            The event that we want to trigger this instance of the tuned test\n            on.\n            ',
            tunable_type=RelationshipTestEvents,
            default=RelationshipTestEvents.AllRelationshipEvents),
        'verify_tunable_callback':
        _verify_tunable_callback
    }
    __slots__ = ('test_events', 'subject', 'required_relationship_bits',
                 'prohibited_relationship_bits', 'track',
                 'relationship_score_interval', 'initiated')

    def __init__(self,
                 subject,
                 required_relationship_bits,
                 prohibited_relationship_bits,
                 track,
                 relationship_score_interval,
                 test_event,
                 initiated=True,
                 **kwargs):
        super().__init__(**kwargs)
        if test_event == RelationshipTestEvents.AllRelationshipEvents:
            self.test_events = (TestEvent.RelationshipChanged,
                                TestEvent.AddRelationshipBit,
                                TestEvent.RemoveRelationshipBit)
        else:
            self.test_events = (test_event, )
        self.subject = subject
        self.required_relationship_bits = required_relationship_bits
        self.prohibited_relationship_bits = prohibited_relationship_bits
        self.track = track
        self.relationship_score_interval = relationship_score_interval
        self.initiated = initiated

    @cached_test
    def __call__(self, targets=None):
        if not self.initiated:
            return TestResult.TRUE
        if targets is None:
            return TestResult(
                False,
                'Currently Actor-only relationship tests are unsupported, valid on zone load.'
            )
        if self.track is None:
            self.track = singletons.DEFAULT

    def goal_value(self):
        if self.num_relations:
            return self.num_relations
        return 1
class RelationshipBit(HasTunableReference,
                      metaclass=HashedTunedInstanceMetaclass,
                      manager=services.relationship_bit_manager()):
    __qualname__ = 'RelationshipBit'
    INSTANCE_TUNABLES = {
        'display_name':
        TunableLocalizedString(
            description=
            '\n            Localized name of this bit\n            ',
            export_modes=ExportModes.All),
        'bit_description':
        TunableLocalizedString(
            description=
            '\n            Localized description of this bit\n            ',
            export_modes=ExportModes.All),
        'icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed for the relationship bit.\n            ',
            default='PNG:missing_image',
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'bit_added_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is added.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'bit_removed_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is removed.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'depth':
        Tunable(
            description=
            '\n            The amount of depth provided by the bit.\n            ',
            tunable_type=int,
            default=0),
        'priority':
        Tunable(
            description=
            '\n            Priority of the bit.  This is used when a bit turns on while a\n            mutually exclusive bit is already on.\n            ',
            tunable_type=float,
            default=0),
        'display_priority':
        Tunable(
            description=
            '\n            The priority of this bit with regards to UI.  Only the highest\n            priority bits are displayed.\n            ',
            tunable_type=int,
            default=0,
            export_modes=ExportModes.All),
        'visible':
        Tunable(
            description=
            "\n            If True, this bit has the potential to be visible when applied,\n            depending on display_priority and the other active bits.  If False,\n            the bit will not be displayed unless it's part of the\n            REL_INSPECTOR_TRACK bit track.\n            ",
            tunable_type=bool,
            default=True),
        'group_id':
        TunableEnumEntry(
            description=
            '\n            The group this bit belongs to.  Two bits of the same group cannot\n            belong in the same set of bits for a given relationship.\n            ',
            tunable_type=RelationshipBitType,
            default=RelationshipBitType.NoGroup),
        'triggered_track':
        TunableReference(
            description=
            '\n            If set, the track that is triggered when this bit is set\n            ',
            manager=services.statistic_manager(),
            class_restrictions='RelationshipTrack'),
        'required_bits':
        TunableList(
            description=
            '\n            List of all bits that are required to be on in order to allow this\n            bit to turn on.\n            ',
            tunable=TunableReference(services.relationship_bit_manager())),
        'timeout':
        TunableSimMinute(
            description=
            '\n            The length of time this bit will last in sim minutes.  0 means the\n            bit will never timeout.\n            ',
            default=0),
        'remove_on_threshold':
        OptionalTunable(tunable=TunableTuple(
            description=
            '\n                If enabled, this bit will be removed when the referenced track\n                reaches the appropriate threshold.\n                ',
            track=TunableReference(
                description=
                '\n                    The track to be tested.\n                    ',
                manager=services.statistic_manager(),
                class_restrictions='RelationshipTrack'),
            threshold=TunableThreshold(
                description=
                '\n                    The threshold at which to remove this bit.\n                    '
            ))),
        'historical_bits':
        OptionalTunable(tunable=TunableList(tunable=TunableTuple(
            age_trans_from=
            TunableEnumEntry(description=
                             '\n                        Age we are transitioning out of.\n                        ',
                             tunable_type=sims.sim_info_types.Age,
                             default=sims.sim_info_types.Age.CHILD),
            new_historical_bit=TunableReference(
                description=
                '\n                        New historical bit the sim obtains\n                        ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.RELATIONSHIP_BIT))))),
        'collection_ids':
        TunableList(tunable=TunableEnumEntry(
            description=
            '\n                The bit collection id this bit belongs to, like family,\n                friends, romance. Default to be All.\n                ',
            tunable_type=RelationshipBitCollectionUid,
            default=RelationshipBitCollectionUid.All,
            export_modes=ExportModes.All)),
        'buffs_on_add_bit':
        TunableList(tunable=TunableTuple(
            buff_ref=buffs.tunable.
            TunableBuffReference(description=
                                 '\n                    Buff that gets added to sim when bit is added.\n                    '
                                 ),
            amount=Tunable(
                description=
                '\n                    If buff is tied to commodity the amount to add to the\n                    commodity.\n                    ',
                tunable_type=float,
                default=1),
            only_add_once=Tunable(
                description=
                '\n                    If True, the buff should only get added once no matter how\n                    many times this bit is being applied.\n                    ',
                tunable_type=bool,
                default=False))),
        'buffs_to_add_if_on_active_lot':
        TunableList(
            description=
            "\n            List of buffs to add when a sim that I share this relationship with\n            is in the household that owns the lot that I'm on.\n            ",
            tunable=buffs.tunable.TunableBuffReference(
                description=
                '\n                Buff that gets added to sim when bit is added.\n                '
            )),
        'permission_requirements':
        TunableList(
            tunable=TunableTuple(
                permission=TunableEnumEntry(
                    description=
                    '\n                    The Sim Permission to test to allow setting of the\n                    relationship bit.\n                    ',
                    tunable_type=SimPermissions.Settings,
                    default=SimPermissions.Settings.VisitationAllowed),
                required_enabled=
                Tunable(
                    description=
                    '\n                    If True, the chosen Sim Permission must be enabled for\n                    relationship bit to be set.  If False, the permission must\n                    be disabled.\n                    ',
                    tunable_type=bool,
                    default=True))),
        'autonomy_multiplier':
        Tunable(
            description=
            '\n            This value is multiplied to the autonomy score of any interaction\n            performed between the two Sims.  For example, when the Sim decides\n            to socialize, she will start looking at targets to socialize with.\n            If there is a Sim who she shares this bit with, her final score for\n            socializing with that Sim will be multiplied by this value.\n            ',
            tunable_type=float,
            default=1),
        'prevents_relationship_culling':
        Tunable(
            description=
            '\n            If checked, any relationship with this bit applied will never be\n            culled.\n            ',
            tunable_type=bool,
            default=False),
        'persisted_tuning':
        Tunable(
            description=
            '\n            Whether this bit will persist when saving a Sim. \n            \n            For example, a Sims is good_friends should be set to true, but\n            romantic_gettingMarried should not be saved.\n            ',
            tunable_type=bool,
            default=True)
    }
    is_track_bit = False
    is_rel_bit = True
    track_min_score = 0
    track_max_score = 0
    track_mean_score = 0
    trait_replacement_bits = None

    @sims4.utils.classproperty
    def persisted(cls):
        return cls.persisted_tuning

    def __init__(self):
        self._buff_handles = []
        self._conditional_removal_listener = None
        self._appropriate_buffs_handles = []

    def on_add_to_relationship(self, sim, target_sim_info, relationship):
        for buff_data in self.buffs_on_add_bit:
            buff_type = buff_data.buff_ref.buff_type
            if buff_data.only_add_once:
                if buff_type.guid64 in relationship.bit_added_buffs[
                        buff_type.guid64]:
                    pass
                else:
                    relationship.bit_added_buffs[buff_type.guid64].append(
                        buff_type.guid64)
            if buff_type.commodity:
                tracker = sim.get_tracker(buff_type.commodity)
                tracker.add_value(buff_type.commodity, buff_data.amount)
                sim.set_buff_reason(buff_type, buff_data.buff_ref.buff_reason)
            else:
                buff_handle = sim.add_buff(
                    buff_type, buff_reason=buff_data.buff_ref.buff_reason)
                self._buff_handles.append(buff_handle)
        if self.bit_added_notification is not None and sim.is_selectable:
            target_sim = target_sim_info.get_sim_instance()
            if not target_sim or not target_sim.is_selectable:
                self._show_bit_added_dialog(sim, target_sim_info)
            elif not target_sim_info.relationship_tracker.has_bit(
                    sim.id, type(self)):
                self._show_bit_added_dialog(sim, target_sim_info)

    def on_remove_from_relationship(self, sim, target_sim_info):
        for buff_handle in self._buff_handles:
            sim.remove_buff(buff_handle)
        if self.bit_removed_notification is not None and sim.is_selectable:
            target_sim = target_sim_info.get_sim_instance()
            if not target_sim or not target_sim.is_selectable:
                self._show_bit_removed_dialog(sim, target_sim_info)
            elif not target_sim_info.relationship_tracker.has_bit(
                    sim.id, type(self)):
                self._show_bit_removed_dialog(sim, target_sim_info)

    def add_appropriateness_buffs(self, sim_info):
        if not self._appropriate_buffs_handles:
            for buff in self.buffs_to_add_if_on_active_lot:
                handle = sim_info.add_buff(buff.buff_type,
                                           buff_reason=buff.buff_reason)
                self._appropriate_buffs_handles.append(handle)

    def remove_appropriateness_buffs(self, sim_info):
        for buff in self._appropriate_buffs_handles:
            sim_info.remove_buff(buff)
        self._appropriate_buffs_handles.clear()

    def add_conditional_removal_listener(self, listener):
        if self._conditional_removal_listener is not None:
            logger.error(
                'Attempting to add a conditional removal listener when one already exists; old one will be overwritten.',
                owner='rez')
        self._conditional_removal_listener = listener

    def remove_conditional_removal_listener(self):
        listener = self._conditional_removal_listener
        self._conditional_removal_listener = None
        return listener

    def __repr__(self):
        return '<({}) Type: {}.{}>'.format(self.__name__,
                                           self.__mro__[1].__module__,
                                           self.__mro__[1].__name__)

    @classmethod
    def _cls_repr(cls):
        return '<({}) Type: {}.{}>'.format(cls.__name__,
                                           cls.__mro__[1].__module__,
                                           cls.__mro__[1].__name__)

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.historical_bits is not None:
            for bit in cls.historical_bits:
                pass
        if cls.remove_on_threshold and cls.remove_on_threshold.track is None:
            logger.error(
                'Tuning Error: Remove On Threshold was tuned without a corresponding relationship track.'
            )

    def _show_bit_added_dialog(self, sim, target_sim_info):
        dialog = self.bit_added_notification(
            sim, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    def _show_bit_removed_dialog(self, sim, target_sim_info):
        dialog = self.bit_removed_notification(
            sim, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    @classmethod
    def matches_bit(cls, bit_type):
        return cls is bit_type