示例#1
0
class SkillThreshold(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'skill_threshold':
        TunableThreshold(
            description=
            '\n            The Threshold for the skill level to be valid.\n            ',
            value=Tunable(
                description=
                '\n                The value of a threshold.\n                ',
                tunable_type=int,
                default=0))
    }

    @property
    def skill_range_max(self):
        comparison_operator = sims4.math.Operator.from_function(
            self.skill_threshold.comparison)
        if comparison_operator == sims4.math.Operator.LESS_OR_EQUAL or comparison_operator == sims4.math.Operator.LESS or comparison_operator == sims4.math.Operator.EQUAL:
            return self.skill_threshold.value
        else:
            return statistics.skill.MAX_SKILL_LEVEL

    @property
    def skill_range_min(self):
        comparison_operator = sims4.math.Operator.from_function(
            self.skill_threshold.comparison)
        if comparison_operator == sims4.math.Operator.GREATER_OR_EQUAL or comparison_operator == sims4.math.Operator.GREATER or comparison_operator == sims4.math.Operator.EQUAL:
            return self.skill_threshold.value
        else:
            return 0

    def __call__(self, curr_value):
        if not self.skill_threshold.compare(curr_value):
            return TestResult(False, 'Skill failed threshold test.')
        return TestResult.TRUE
示例#2
0
class OffspringCreatedTest(HasTunableSingletonFactory, AutoFactoryInit,
                           event_testing.test_base.BaseTest):
    __qualname__ = 'OffspringCreatedTest'
    test_events = (event_testing.test_events.TestEvent.OffspringCreated, )
    USES_EVENT_DATA = True
    FACTORY_TUNABLES = {
        'description':
        'This test checks for a tuned number of offspring to have been created upon\n        the moment of the DeliverBabySuperInteraction completion.',
        'offspring_threshold':
        TunableThreshold(
            description=
            '\n            The comparison of amount of offspring created to the number desired.\n            '
        )
    }

    def get_expected_args(self):
        return {'offspring_created': event_testing.test_events.FROM_EVENT_DATA}

    @cached_test
    def __call__(self, offspring_created=None):
        if offspring_created is None:
            return TestResult(
                False,
                'OffspringCreatedTest: Offspring count is empty, valid during zone load.'
            )
        if not self.offspring_threshold.compare(offspring_created):
            return TestResult(
                False,
                'OffspringCreatedTest: Not the desired amount of offspring created. {} {}',
                offspring_created, self.offspring_threshold)
        return TestResult.TRUE
class GroupBasedCondition(Condition, HasTunableFactory):
    __qualname__ = 'GroupBasedCondition'
    FACTORY_TUNABLES = {'threshold': TunableThreshold(description='Threshold tested against group size.')}

    def __init__(self, threshold, **kwargs):
        super().__init__(**kwargs)
        self._threshold = threshold
        self._social_group = None
        self._previously_satisfied = self._threshold.compare(0)

    def __str__(self):
        return 'Group Threshold: {}'.format(self._threshold)

    def _group_changed(self, group):
        if self._threshold.compare(group.get_active_sim_count()):
            self._previously_satisfied = True
            self._satisfy()
        else:
            self._previously_satisfied = False

    def attach_to_owner(self, owner, callback):
        self.si_callback = callback
        self._owner = owner
        social_group_owner = owner.super_interaction
        self._social_group = social_group_owner.social_group
        self._social_group.on_group_changed.append(self._group_changed)
        self._group_changed(self._social_group)
        return (None, None)

    def detach_from_owner(self, *_, **__):
        if self._social_group is not None and self._group_changed in self._social_group.on_group_changed:
            self._social_group.on_group_changed.remove(self._group_changed)
class BaseCivicPolicyTestVotingTimeUntilChange(HasTunableSingletonFactory,
                                               AutoFactoryInit):
    FACTORY_TUNABLES = {
        'threshold':
        TunableThreshold(
            description=
            '\n            The amount of time relative to the next voting state change to test.\n            ',
            value=TunableTimeSpan(
                description=
                '\n                Duration before the next change.\n                ',
                default_hours=1))
    }

    def get_custom_event_keys(self, provider_owner):
        return []

    def run_test(self, provider, tooltip):
        street_service = services.street_service()
        voting_open = street_service is not None and street_service.voting_open
        if voting_open:
            time_of_change = street_service.voting_close_time
        else:
            time_of_change = street_service.voting_open_time
        now = services.time_service().sim_now
        time_left = time_of_change - now
        if callable(self.threshold.value):
            self.threshold.value = self.threshold.value()
        result = self.threshold.compare(time_left)
        if not result:
            return TestResult(
                False,
                'Civic Policy Provider failed voting time until change test',
                tooltip=tooltip)
        return TestResult.TRUE
示例#5
0
class DistanceTest(HasTunableSingletonFactory, AutoFactoryInit, BaseTest):
    FACTORY_TUNABLES = {'threshold': TunableThreshold(description='\n            The distance threshold for this test. The distance between the\n            subject and the target must satisfy this condition in order of the\n            test to pass.\n            '), 'level_modifier': TunableVariant(description='\n            Determine how difference in levels affects distance. A modifier of\n            10, for example, would mean that the distance between two objects is\n            increased by 10 meters for every floor between them.\n            ', specific=TunableRange(description='\n                A meter modifier to add to the distance multiplied by the number\n                of floors between subject and target.\n                ', tunable_type=float, minimum=0, default=8), locked_args={'no_modifier': 0, 'infinite': None}, default='no_modifier'), 'subject': TunableEnumEntry(description='\n            The subject of the test.\n            ', tunable_type=ParticipantType, default=ParticipantType.Actor), 'target': TunableEnumEntry(description='\n            The target of the test.\n            ', tunable_type=ParticipantType, default=ParticipantType.Object)}

    def get_expected_args(self):
        return {'subjects': self.subject, 'targets': self.target}

    @cached_test
    def __call__(self, subjects=(), targets=()):
        for subject in subjects:
            if subject.is_sim:
                subject = subject.get_sim_instance()
            for target in targets:
                if target.is_sim:
                    target = target.get_sim_instance()
                if subject is None or target is None:
                    distance = sims4.math.MAX_INT32
                else:
                    distance = (target.position - subject.position).magnitude()
                    level_difference = abs(subject.routing_surface.secondary_id - target.routing_surface.secondary_id)
                    if level_difference:
                        if self.level_modifier is None:
                            distance = sims4.math.MAX_INT32
                        else:
                            distance += level_difference*self.level_modifier
                if not self.threshold.compare(distance):
                    return TestResult(False, 'Distance test failed.', tooltip=self.tooltip)
        return TestResult.TRUE

    def validate_tuning_for_objective(self, objective):
        if self.threshold.value == 0:
            logger.error('Error in objective {} in DistanceTest. Threshold has value of 0.', objective)
示例#6
0
class TotalSimoleonsEarnedByTagTest(event_testing.test_base.BaseTest):
    __qualname__ = 'TotalSimoleonsEarnedByTagTest'
    test_events = (TestEvent.SimoleonsEarned, )
    USES_DATA_OBJECT = True
    FACTORY_TUNABLES = {
        'description':
        'Test for the total simoleons earned by selling objects tagged with tag_to_test.',
        'tag_to_test':
        TunableEnumEntry(Tag,
                         Tag.INVALID,
                         description='The tags on the objects for selling.'),
        'threshold':
        TunableThreshold(description='Amount in Simoleons required to pass')
    }

    def __init__(self, tag_to_test, threshold, **kwargs):
        super().__init__(**kwargs)
        self.tag_to_test = tag_to_test
        self.threshold = threshold

    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):
        total_simoleons_earned = data_object.get_total_tag_simoleons_earned(
            self.tag_to_test)
        relative_start_value = data_object.get_starting_values(
            objective_guid64)
        if relative_start_value is not None:
            simoleons = 0
            total_simoleons_earned -= relative_start_value[simoleons]
        if self.threshold.compare(total_simoleons_earned):
            return TestResult.TRUE
        return TestResultNumeric(
            False,
            'TotalSimoleonsEarnedByTagTest: Not enough Simoleons earned on tag{}.',
            self.tag_to_test,
            current_value=total_simoleons_earned,
            goal_value=self.threshold.value,
            is_money=True)

    def save_relative_start_values(self, objective_guid64, data_object):
        total_simoleons_earned = data_object.get_total_tag_simoleons_earned(
            self.tag_to_test)
        data_object.set_starting_values(objective_guid64,
                                        [total_simoleons_earned])

    def tuning_is_valid(self):
        return self.tag_to_test is not Tag.INVALID and self.threshold.value != 0

    def goal_value(self):
        return self.threshold.value
示例#7
0
class LockRankedStatisticData(LockData):
    REFRESH_EVENTS = (TestEvent.RankedStatisticChange, )
    FACTORY_TUNABLES = {
        'ranked_stat':
        TunableReference(
            description=
            "\n            The ranked statistic we are operating on. Sims won't be allowed to\n            traverse if they don't have this statistic.\n            ",
            manager=services.statistic_manager(),
            class_restrictions=('RankedStatistic', )),
        'rank_threshold':
        TunableThreshold(
            description=
            "\n            Sims that have ranked statistic's value inside the threshold are \n            not locked by the portal.\n            ",
            value=TunableRange(
                description=
                '\n                The number that describes the threshold.\n                ',
                tunable_type=int,
                default=1,
                minimum=0),
            default=sims4.math.Threshold(1, operator.ge))
    }

    def __init__(self, **kwargs):
        super().__init__(lock_type=LockType.LOCK_RANK_STATISTIC, **kwargs)

    def __repr__(self):
        return 'Ranked Stat: {}, Threshold: {}'.format(self.ranked_stat,
                                                       self.rank_threshold)

    def test_lock(self, sim):
        tracker = sim.sim_info.get_tracker(self.ranked_stat)
        if tracker is not None:
            ranked_stat_inst = tracker.get_statistic(self.ranked_stat)
            if ranked_stat_inst is not None and self.rank_threshold.compare(
                    ranked_stat_inst.rank_level):
                return LockResult(False, self.lock_type, self.lock_priority,
                                  self.lock_sides)
        return LockResult(True, self.lock_type, self.lock_priority,
                          self.lock_sides)

    def save(self, save_data):
        super().save(save_data)
        save_data.ranked_stat_id = self.ranked_stat.guid64
        save_data.threshold_value = self.rank_threshold.value
        save_data.threshold_comparison = Operator.from_function(
            self.rank_threshold.comparison)

    def load(self, load_data):
        super().load(load_data)
        self.ranked_stat = services.statistic_manager().get(
            load_data.ranked_stat_id)
        self.rank_threshold = Threshold(
            value=load_data.threshold_value,
            comparison=Operator(load_data.threshold_comparison).function)
示例#8
0
 def __init__(self, **kwargs):
     super().__init__(
         stat_to_check=TunableReference(services.statistic_manager()),
         threshold=TunableThreshold(
             description=
             'Stat should be greater than this value for object creation to score.'
         ),
         multiplier=Tunable(
             float,
             1,
             description=
             'Multiplier to be applied to score if object is created with this quality'
         ))
示例#9
0
class TrackCommodity(HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'commodity_to_track':
        TunableReference(
            description=
            '\n            The commodity that we want to track on the Sim.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC),
            class_restrictions=('Commodity', )),
        'threshold':
        TunableThreshold(
            description=
            '\n            The threshold that we are trying to reach.\n            '
        )
    }

    def __init__(self, event_data_tracker, milestone, objective, **kwargs):
        super().__init__(**kwargs)
        self._event_data_tracker = event_data_tracker
        self._owning_milestone = milestone
        self._owning_objective = objective
        self._commodity_tracker_handle = None

    def on_threshold_reached(self, stat_type):
        self._event_data_tracker.tracker_complete(self._owning_milestone,
                                                  self._owning_objective)

    def setup(self):
        sim_info = self._event_data_tracker.owner_sim_info
        if sim_info is None:
            return False
        if self._commodity_tracker_handle is not None:
            self._commodity_tracker_handle = sim_info.commodity_tracker.create_and_add_listener(
                self.commodity_to_track, self.threshold,
                self.on_threshold_reached)
        return True

    def clear(self):
        if self._commodity_tracker_handle is not None:
            sim_info = self._event_data_tracker.owner_sim_info
            sim_info.commodity_tracker.remove_listener(
                self._commodity_tracker_handle)
        self._commodity_tracker_handle = None
        self._event_data_tracker = None
        self._owning_milestone = None
        self._owning_objective = None
示例#10
0
class RestaurantCourseItemCountTest(HasTunableSingletonFactory,
                                    AutoFactoryInit, test_base.BaseTest):
    FACTORY_TUNABLES = {
        'course':
        TunableEnumWithFilter(
            description=
            '\n            The course to check for this test.\n            ',
            tunable_type=Tag,
            filter_prefixes=['recipe_course'],
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True),
        'threshold':
        TunableThreshold(
            description=
            '\n            The number of items that should available in this course.\n            '
        ),
        'blacklist_recipes':
        TunableList(
            description=
            '\n            The items from the course to not include in this test.\n            ',
            tunable=TunableReference(manager=services.recipe_manager(),
                                     class_restrictions=('Recipe', ),
                                     pack_safe=True))
    }

    def get_expected_args(self):
        return {}

    def __call__(self):
        zone_director = get_restaurant_zone_director()
        if zone_director is None:
            return TestResult(
                False,
                'Want to test restaurant course item count but not in a restaurant.',
                tooltip=self.tooltip)
        item_count = len([
            recipe for recipe in zone_director.get_menu_for_course(self.course)
            if recipe not in self.blacklist_recipes
        ])
        if not self.threshold.compare(item_count):
            return TestResult(False,
                              'Only {} items in {}'.format(
                                  item_count, self.course),
                              tooltip=self.tooltip)
        return TestResult.TRUE
class TestBasedScoreThresholdTest(event_testing.test_base.BaseTest):
    __qualname__ = 'TestBasedScoreThresholdTest'
    FACTORY_TUNABLES = {
        'description':
        'Gate availability by a statistic on the actor or target.',
        'who':
        TunableEnumEntry(ParticipantType,
                         ParticipantType.Actor,
                         description='Who or what to apply this test to.'),
        'test_based_score':
        TunableReference(services.test_based_score_manager(),
                         description='The specific cumulative test.'),
        'threshold':
        TunableThreshold(
            description=
            "The threshold to control availability based on the statistic's value"
        )
    }

    def __init__(self, who, test_based_score, threshold, **kwargs):
        super().__init__(safe_to_skip=True, **kwargs)
        self.who = who
        self.test_based_score = test_based_score
        self.threshold = threshold

    def get_expected_args(self):
        return {'resolver': RESOLVER_PARTICIPANT}

    def __call__(self, resolver=None):
        score = self.test_based_score.get_score(resolver)
        if not self.threshold.compare(score):
            operator_symbol = Operator.from_function(
                self.threshold.comparison).symbol
            return TestResult(
                False,
                'Failed {}. Current Score: {}. Operator: {}. Threshold: {}',
                self.test_based_score.__name__,
                score,
                operator_symbol,
                self.threshold,
                tooltip=self.tooltip)
        return TestResult.TRUE

    def tuning_is_valid(self):
        return self.test_based_score is not None
示例#12
0
class TestBasedScoreThresholdTest(event_testing.test_base.BaseTest):
    FACTORY_TUNABLES = {
        'description':
        'Gate availability by a statistic on the actor or target.',
        'who':
        TunableEnumEntry(ParticipantType,
                         ParticipantType.Actor,
                         description='Who or what to apply this test to.'),
        'test_based_score':
        TunableReference(
            services.test_based_score_manager(),
            description=
            'The specific cumulative test.  This is pack safe because this particular test was being used for module tuning, so be careful that you are not referencing from one pack to the next.',
            pack_safe=True),
        'threshold':
        TunableThreshold(
            description=
            "The threshold to control availability based on the statistic's value"
        )
    }

    def __init__(self, who, test_based_score, threshold, **kwargs):
        super().__init__(safe_to_skip=True, **kwargs)
        self.who = who
        self.test_based_score = test_based_score
        self.threshold = threshold

    def get_expected_args(self):
        return {'resolver': RESOLVER_PARTICIPANT}

    def __call__(self, resolver=None):
        if self.test_based_score is None:
            return TestResult(False, 'Failed, no test_based_score provided.')
        if not self.test_based_score.passes_threshold(resolver,
                                                      self.threshold):
            operator_symbol = Operator.from_function(
                self.threshold.comparison).symbol
            return TestResult(False,
                              'Failed {}. Operator: {}. Threshold: {}',
                              self.test_based_score.__name__,
                              operator_symbol,
                              self.threshold,
                              tooltip=self.tooltip)
        return TestResult.TRUE
示例#13
0
 def __init__(self, tunable, description='A tunable blacklist.', **kwargs):
     super().__init__(
         blacklist=TunableSet(
             description=
             '\n                Blacklisted items.\n                ',
             tunable=tunable),
         threshold=OptionalTunable(
             description=
             '\n                Tunable option for how many items must be in the blacklist\n                for the blacklist to fail when testing a collection of items.\n                By default, only one object needs to be in the list.\n                ',
             tunable=TunableThreshold(
                 description=
                 '\n                    When testing a collection of items, the number of items in\n                    that collection that are in the blacklist must pass this\n                    threshold test for the blacklist to disallow them all.\n                    ',
                 value=TunableRange(tunable_type=int, default=1, minimum=0),
                 default=Threshold(1, operator.ge)),
             enabled_by_default=True,
             disabled_name='all_must_match',
             enabled_name='threshold'),
         description=description,
         **kwargs)
class ObjectRelationshipComponent(
        Component,
        HasTunableFactory,
        AutoFactoryInit,
        component_name=types.OBJECT_RELATIONSHIP_COMPONENT,
        persistence_key=protocols.PersistenceMaster.PersistableData.
        ObjectRelationshipComponent):
    FACTORY_TUNABLES = {
        'number_of_allowed_relationships':
        OptionalTunable(
            description=
            '\n            Number of Sims who can have a relationship with this object at one\n            time.  If not specified, an infinite number of Sims can have a \n            relationship with the object.\n            ',
            tunable=TunableRange(tunable_type=int, default=1, minimum=1)),
        'icon_override':
        OptionalTunable(
            description=
            "\n            If enabled, this will override the object's thumbnail generated \n            default icon on Relationship panel.\n            ",
            tunable=TunableResourceKey(
                description=
                '\n                The icon to be displayed in the Relationship panel.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE,
                export_modes=ExportModes.All)),
        'relationship_stat':
        Statistic.TunableReference(
            description=
            "\n            The statistic which will be created for each of this object's\n            relationships.\n            "
        ),
        'relationship_track_visual':
        OptionalTunable(
            description=
            '\n            If enabled, the relationship track to send the client and where\n            it should be displayed. If this is None then this relationship will \n            not be sent down to the client.\n            ',
            tunable=TunableTuple(
                relationship_track=RelationshipTrack.TunableReference(
                    description=
                    '\n                    The relationship that this track will visually try and imitate in\n                    regards to static track tack data.\n                    '
                ),
                visible_in_relationship_panel=Tunable(
                    description=
                    "\n                    By default the relationship is visible in the relationship \n                    panel and the object's tooltip. If this is set to false, \n                    hide the relationship from the relationship panel. \n                    ",
                    tunable_type=bool,
                    default=True))),
        'relationship_based_state_change_tuning':
        OptionalTunable(
            TunableTuple(
                description=
                '\n            A list of value ranges and associated states.  If the active Sim\n            has a relationship with this object  that falls within one of the\n            value ranges specified here, the object will change state to match\n            the specified state.\n            \n            These state changes exist on a per Sim basis, so this tuning will\n            effectively make the same object appear different depending on\n            which Sim is currently active.\n            ',
                state_changes=TunableList(
                    tunable=TunableTuple(
                        value_threshold=TunableThreshold(
                            description=
                            "\n                        The range that the active Sim's relationship with this\n                        object must fall within in order for this state change to\n                        take place.\n                        "
                        ),
                        state=TunableStateValueReference(
                            description=
                            "\n                        The state this object will change to if it's relationship\n                        with the active Sim falls within the specified range.\n                        "
                        ))),
                default_state=TunableStateValueReference(
                    description=
                    '\n                The state this object will change to if there is no other tuned\n                relationship based state change for the currently active Sim.\n                '
                )))
    }

    def __init__(self, owner, **kwargs):
        super().__init__(owner, **kwargs)
        self._state_changes = None
        self._default_state = None
        self._object_social_mixin = None
        self._relationships = {}
        self._relationship_changed_callbacks = defaultdict(CallableList)
        self._definition_changed_in_buildbuy = False

    @staticmethod
    def setup_relationship(sim, target_object):
        if target_object.objectrelationship_component is None:
            logger.error(
                "Failed to add object relationship because {} doesn't have objectrelationship_component tuned",
                target_object)
            return
        if target_object.objectrelationship_component.has_relationship(sim.id):
            logger.error('Relationship already exists between {} and {}.', sim,
                         target_object)
            return
        if not target_object.objectrelationship_component.add_relationship(
                sim.id):
            logger.error(
                'Failed to add new object relationship between {} and {}.',
                sim, target_object)

    @property
    def relationships(self):
        return self._relationships

    def get_number_of_allowed_relationships(self):
        return self.number_of_allowed_relationships

    def _on_active_sim_change(self, _, new_sim):
        if new_sim is None:
            return
        relationship = self._get_relationship(new_sim.id)
        self._update_state(relationship)

    def _update_state(self, relationship):
        if self._default_state is None:
            return
        if relationship is None:
            new_state = self._default_state
        elif self._state_changes is None:
            new_state = self._default_state
        else:
            for state_change in self._state_changes:
                if state_change.value_threshold.compare(
                        relationship.get_value()):
                    new_state = state_change.state
                    break
            else:
                new_state = self._default_state
        self.owner.set_state(new_state.state, new_state)

    @property
    def _can_add_new_relationship(self):
        if self.number_of_allowed_relationships is not None and len(
                self._relationships) >= self.number_of_allowed_relationships:
            return False
        return True

    def on_add(self):
        services.current_zone().register_callback(
            zone_types.ZoneState.HOUSEHOLDS_AND_SIM_INFOS_LOADED,
            self._publish_relationship_data)
        if self.relationship_based_state_change_tuning is None:
            return
        self._state_changes = self.relationship_based_state_change_tuning.state_changes
        self._default_state = self.relationship_based_state_change_tuning.default_state
        services.current_zone().register_callback(
            zone_types.ZoneState.CLIENT_CONNECTED,
            self._register_active_sim_change)

    def on_remove(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.unregister_active_sim_changed(self._on_active_sim_change)
        self.owner.remove_name_changed_callback(self._on_name_changed)
        self.destroy_all_relationship()

    def apply_definition(self, definition, obj_state=0):
        if not services.current_zone().is_in_build_buy:
            return
        self._definition_changed_in_buildbuy |= self.owner.definition != definition

    def on_buildbuy_exit(self):
        if not self._definition_changed_in_buildbuy:
            return
        self._publish_relationship_data()
        self._definition_changed_in_buildbuy = False

    def _register_active_sim_change(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.register_active_sim_changed(self._on_active_sim_change)

    def _publish_relationship_data(self):
        if self.relationship_track_visual is None:
            return
        for sim_id in self._relationships.keys():
            self._send_relationship_data(sim_id)

    def _update_object_relationship_name(self):
        ownable_component = self.owner.get_component(types.OWNABLE_COMPONENT)
        if ownable_component is not None:
            sim_owner_id = ownable_component.get_sim_owner_id()
            obj_def_id = self.owner.definition.id
            relationship_service = services.relationship_service()
            obj_tag_set = relationship_service.get_mapped_tag_set_of_id(
                obj_def_id)
            if obj_tag_set is not None:
                obj_relationship = relationship_service.get_object_relationship(
                    sim_owner_id, obj_tag_set)
                if obj_relationship is not None and self.owner.has_custom_name(
                ):
                    obj_relationship.set_object_rel_name(
                        self.owner.custom_name)

    def _on_name_changed(self, *_, **__):
        self._publish_relationship_data()
        self._update_object_relationship_name()

    def add_relationship_changed_callback_for_sim_id(self, sim_id, callback):
        self._relationship_changed_callbacks[sim_id].append(callback)

    def remove_relationship_changed_callback_for_sim_id(
            self, sim_id, callback):
        if sim_id in self._relationship_changed_callbacks and callback in self._relationship_changed_callbacks[
                sim_id]:
            self._relationship_changed_callbacks[sim_id].remove(callback)

    def _trigger_relationship_changed_callbacks_for_sim_id(self, sim_id):
        callbacks = self._relationship_changed_callbacks[sim_id]
        if callbacks is not None:
            callbacks()

    def add_relationship(self, sim_id):
        if sim_id in self._relationships:
            return False
        if not self._can_add_new_relationship:
            return False
        self.owner.on_hovertip_requested()
        stat = self.relationship_stat(None)
        self._relationships[sim_id] = stat
        stat.on_add()
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        self.owner.add_name_changed_callback(self._on_name_changed)
        return True

    def remove_relationship(self, sim_id):
        if sim_id not in self._relationships:
            return
        del self._relationships[sim_id]
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        self._send_relationship_destroy(sim_id)

    def destroy_all_relationship(self):
        sim_ids = list(self._relationships.keys())
        for sim_id in sim_ids:
            self.remove_relationship(sim_id)

    def modify_relationship(self, sim_id, value, add=True, from_load=False):
        if sim_id not in self._relationships:
            if not add:
                return
            if not self.add_relationship(sim_id):
                return
        if from_load:
            self._relationships[sim_id].set_value(value)
        else:
            self._relationships[sim_id].add_value(value)
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        client = services.client_manager().get_first_client()
        if client is not None and client.active_sim is not None and client.active_sim.sim_id == sim_id:
            self._update_state(self._relationships[sim_id])

    def on_social_start(self, sim):
        if self.has_relationship(sim.id) or self.add_relationship(sim.id):
            self._object_social_mixin = ObjectRelationshipSocialMixin(
                sim, self.owner.id, self._get_relationship(sim.id))
            self._object_social_mixin.send_social_start_message()

    def on_social_end(self):
        if self._object_social_mixin is not None:
            self._object_social_mixin.send_social_end_message()
            self._object_social_mixin = None

    def _get_relationship(self, sim_id):
        return self._relationships.get(sim_id)

    def has_relationship(self, sim_id):
        return sim_id in self._relationships

    def get_relationship_value(self, sim_id):
        relationship = self._get_relationship(sim_id)
        if relationship is not None:
            return relationship.get_value()
        return self.relationship_stat.initial_value

    def get_relationship_initial_value(self):
        return self.relationship_stat.initial_value

    def get_relationship_max_value(self):
        return self.relationship_stat.max_value

    def get_relationship_min_value(self):
        return self.relationship_stat.min_value

    def _send_relationship_data(self, sim_id):
        if self.relationship_track_visual is None:
            return
        relationship_to_send = self._get_relationship(sim_id)
        if not relationship_to_send:
            return
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return
        msg = commodity_protocol.RelationshipUpdate()
        msg.actor_sim_id = sim_id
        (msg.target_id.object_id,
         msg.target_id.manager_id) = self.owner.icon_info
        msg.target_instance_id = self.owner.id
        if self.icon_override is not None:
            build_icon_info_msg(IconInfoData(icon_resource=self.icon_override),
                                None, msg.target_icon_override)
        with ProtocolBufferRollback(msg.tracks) as relationship_track_update:
            relationship_value = relationship_to_send.get_value()
            relationship_track_update.track_score = relationship_value
            relationship_track_update.track_bit_id = self.relationship_track_visual.relationship_track.get_bit_at_relationship_value(
                relationship_value).guid64
            relationship_track_update.track_id = self.relationship_track_visual.relationship_track.guid64
            relationship_track_update.track_popup_priority = self.relationship_track_visual.relationship_track.display_popup_priority
            relationship_track_update.visible_in_relationship_panel = self.relationship_track_visual.visible_in_relationship_panel
        send_relationship_op(sim_info, msg)
        if self._object_social_mixin is not None:
            self._object_social_mixin.send_social_update_message()

    def _send_relationship_destroy(self, sim_id):
        if self.relationship_track_visual is None or self.relationship_track_visual.relationship_track is None:
            return
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return
        msg = commodity_protocol.RelationshipDelete()
        msg.actor_sim_id = sim_id
        msg.target_id = self.owner.id
        op = GenericProtocolBufferOp(
            DistributorOps_pb2.Operation.SIM_RELATIONSHIP_DELETE, msg)
        distributor = Distributor.instance()
        distributor.add_op(services.sim_info_manager().get(sim_id), op)

    def save(self, persistence_master_message):
        if not self._relationships:
            return
        persistable_data = protocols.PersistenceMaster.PersistableData()
        persistable_data.type = protocols.PersistenceMaster.PersistableData.ObjectRelationshipComponent
        relationship_component_data = persistable_data.Extensions[
            protocols.PersistableObjectRelationshipComponent.persistable_data]
        for (key, value) in self._relationships.items():
            if not value.persisted_tuning:
                continue
            with ProtocolBufferRollback(relationship_component_data.
                                        relationships) as relationship_data:
                relationship_data.sim_id = key
                relationship_data.value = value.get_value()
        persistence_master_message.data.extend([persistable_data])

    def load(self, persistable_data):
        relationship_component_data = persistable_data.Extensions[
            protocols.PersistableObjectRelationshipComponent.persistable_data]
        for relationship in relationship_component_data.relationships:
            self.modify_relationship(relationship.sim_id,
                                     relationship.value,
                                     from_load=True)
示例#15
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
示例#16
0
class CareerAttendenceTest(event_testing.test_base.BaseTest):
    __qualname__ = 'CareerAttendenceTest'
    test_events = (event_testing.test_events.TestEvent.WorkdayComplete, )
    USES_DATA_OBJECT = True
    USES_EVENT_DATA = True
    FACTORY_TUNABLES = {
        'description':
        'After a work day completes, did your sim work a desired of hours, earn a tuned amount (total over lifetime),                            at a specific or any career. Note: any career (leaving career untuned) means it checks against total of all of them.',
        'career_to_test':
        TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.CAREER)),
        'career_category':
        TunableEnumEntry(
            careers.career_tuning.CareerCategory,
            careers.career_tuning.CareerCategory.Invalid,
            description=
            'Category the specified career is required to be in order to pass validation'
        ),
        'simoleons_earned':
        TunableThreshold(description='Amount in Simoleons required to pass'),
        'hours_worked':
        TunableThreshold(description='Amount in hours required to pass')
    }

    def __init__(self, career_to_test, career_category, simoleons_earned,
                 hours_worked, **kwargs):
        super().__init__(**kwargs)
        self.career_to_test = career_to_test
        self.simoleons_earned = simoleons_earned
        self.hours_worked = hours_worked
        self.career_category = career_category

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

    @cached_test
    def __call__(self, career=None, data=None, objective_guid64=None):
        if career is None:
            return TestResult(
                False, 'Career provided is None, valid during zone load.')
        total_money_made = 0
        total_time_worked = 0
        if not isinstance(career, self.career_to_test):
            return TestResult(False, '{} does not match tuned value {}',
                              career, self.career_to_test)
        career_data = data.get_career_data(career)
        total_money_made = career_data.get_money_earned()
        total_time_worked = career_data.get_hours_worked()
        relative_start_values = data.get_starting_values(objective_guid64)
        money = 0
        time = 1
        total_money_made -= relative_start_values[money]
        total_time_worked -= relative_start_values[time]
        if not (self.career_to_test is not None
                and relative_start_values is not None
                and self.simoleons_earned.compare(total_money_made)):
            return TestResultNumeric(
                False,
                'CareerAttendenceTest: not the desired amount of Simoleons.',
                current_value=total_money_made,
                goal_value=self.simoleons_earned.value,
                is_money=True)
        if not self.hours_worked.compare(total_time_worked):
            return TestResultNumeric(
                False,
                'CareerAttendenceTest: not the desired amount of time worked.',
                current_value=total_time_worked,
                goal_value=self.hours_worked.value,
                is_money=False)
        return TestResult.TRUE

    def save_relative_start_values(self, objective_guid64, data_object):
        if self.career_to_test is not None:
            return
        career_name = self.career_to_test.__name__
        start_money = data_object.get_career_data_by_name(
            career_name).get_money_earned()
        start_time = data_object.get_career_data_by_name(
            career_name).get_hours_worked()
        data_object.set_starting_values(objective_guid64,
                                        [start_money, start_time])
示例#17
0
class SimoleonsEarnedTest(event_testing.test_base.BaseTest):
    __qualname__ = 'SimoleonsEarnedTest'
    test_events = (event_testing.test_events.TestEvent.SimoleonsEarned, )
    USES_EVENT_DATA = True
    FACTORY_TUNABLES = {
        'description':
        'Require the participant(s) to (each) earn a specific amount of Simoleons for a skill or tag on an object sold.',
        'event_type_to_test':
        TunableVariant(
            skill_to_test=SkillTestFactory(),
            tags_to_test=TagSetTestFactory(),
            description='Test a skill for an event or tags on an object.'),
        'threshold':
        TunableThreshold(description='Amount in Simoleons required to pass'),
        'household_fund_threshold':
        OptionalTunable(
            description=
            '\n            Restricts test success based on household funds.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Household fund threshold and moment of evaluation.\n                ',
                threshold=TunableThreshold(
                    description=
                    '\n                    Amount of simoleons in household funds required to pass.\n                    '
                ),
                test_before_earnings=Tunable(
                    description=
                    '\n                    If True, threshold will be evaluated before funds were \n                    updated with earnings.\n                    ',
                    tunable_type=bool,
                    default=False)))
    }

    def __init__(self, event_type_to_test, threshold, household_fund_threshold,
                 **kwargs):
        super().__init__(**kwargs)
        self.event_type_to_test = event_type_to_test
        self.threshold = threshold
        self.household_fund_threshold = household_fund_threshold

    def get_expected_args(self):
        return {
            'sims': event_testing.test_events.SIM_INSTANCE,
            'amount': event_testing.test_events.FROM_EVENT_DATA,
            'skill_used': event_testing.test_events.FROM_EVENT_DATA,
            'tags': event_testing.test_events.FROM_EVENT_DATA
        }

    @cached_test
    def __call__(self, sims=None, amount=None, skill_used=None, tags=None):
        if amount is None:
            return TestResultNumeric(
                False,
                'SimoleonsEarnedTest: amount is none, valid during zone load.',
                current_value=0,
                goal_value=self.threshold.value,
                is_money=True)
        if not self.threshold.compare(amount):
            return TestResultNumeric(
                False,
                'SimoleonsEarnedTest: not enough Simoleons earned.',
                current_value=amount,
                goal_value=self.threshold.value,
                is_money=True)
        if not (self.event_type_to_test is not None
                and self.event_type_to_test(skill_used, tags)):
            return TestResult(
                False,
                '\n                    SimoleonsEarnedTest: the skill used to earn Simoleons does\n                    not match the desired skill or tuned tags do not match\n                    object tags.\n                    '
            )
        if self.household_fund_threshold is not None:
            for sim_info in sims:
                household = services.household_manager().get_by_sim_id(
                    sim_info.sim_id)
                if household is None:
                    return TestResult(False,
                                      "Couldn't find household for sim {}",
                                      sim_info)
                household_funds = household.funds.money
                if self.household_fund_threshold.test_before_earnings:
                    household_funds -= amount
                while not self.household_fund_threshold.threshold.compare(
                        household_funds):
                    return TestResult(
                        False,
                        'Threshold test on household funds failed for sim {}',
                        sim_info)
        return TestResult.TRUE

    def goal_value(self):
        return self.threshold.value
示例#18
0
class ClubTest(HasTunableSingletonFactory, AutoFactoryInit,
               event_testing.test_base.BaseTest):
    _AffordanceData = namedtuple('_AffordanceData', ('affordance', 'target'))
    CLUB_USE_ASSOCIATED = 1
    CLUB_USE_ANY = 2
    CLUB_FROM_EVENT_DATA = 3
    AFFORDANCE_RULE_ENCOURAGED = 1
    AFFORDANCE_RULE_DISCOURAGED = 2
    AFFORDANCE_RULE_NOT_ENCOURAGED = 3
    AFFORDANCE_RULE_NOT_DISCOURAGED = 4
    FACTORY_TUNABLES = {
        'subject':
        TunableEnumEntry(
            description=
            '\n            The subject whose Club status to check.\n            ',
            tunable_type=ParticipantType,
            default=ParticipantType.Actor),
        'require_common_club':
        OptionalTunable(
            description=
            '\n            If enabled, then there must be a common Club that both the subject\n            Sim and this specified Sim are in. If the club type is set to "Use\n            Club from Resolver", then both Sims must be in that Club. If the\n            club type is set to "Use Any Club", then there must be one club both\n            the subject Sim and this Sim are in.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The Sim to test against for a common Club. If a multi-Sim\n                participant is specified, the union of their clubs is\n                considered, i.e. the test passes if at least one Sim satisfies\n                the requirements.\n                ',
                tunable_type=ParticipantType,
                default=ParticipantType.TargetSim)),
        'club':
        TunableVariant(
            description=
            '\n            Define the Club to run this test against.\n            ',
            locked_args={
                'use_club_from_resolver': CLUB_USE_ASSOCIATED,
                'use_any_club': CLUB_USE_ANY,
                'from_event_data': CLUB_FROM_EVENT_DATA
            },
            default='use_club_from_resolver'),
        'club_status':
        OptionalTunable(
            TunableVariant(
                description=
                '\n            In enabled, require the tuned "subject" to either be or not be a\n            member of this interaction\'s associated Club.\n            ',
                locked_args={
                    'Member': MEMBER,
                    'Not Member': NOT_MEMBER,
                    'Leader': LEADER,
                    'Not Leader': NOT_LEADER
                })),
        'recent_member_status':
        OptionalTunable(
            description=
            '\n            If specified, the Sim must satisfy recent member status\n            requirements.\n            ',
            tunable=Tunable(
                description=
                '\n                Whether or not the Sim must be a recent member of the Club in\n                order to pass this test.\n                ',
                tunable_type=bool,
                default=True)),
        'room_for_new_members':
        OptionalTunable(
            TunableVariant(
                description=
                '\n            If enabled, require the associated Club to either have room for new\n            members or not have room for new members.\n            ',
                locked_args={
                    'Has Room': True,
                    'Has No Room': False
                })),
        'invite_only':
        OptionalTunable(
            Tunable(
                description=
                '\n            If enabled, require the associated Club to either be invite only or\n            be open to everyone.\n            ',
                tunable_type=bool,
                default=True)),
        'subject_relationship_with_leader':
        OptionalTunable(
            description=
            '\n            If enabled, the tuned subject is required to have a specific\n            relationship with the leader. If the subject and the leader match,\n            the test fails.\n            ',
            tunable=TunableRelationshipTest(
                locked_args={
                    'subject': None,
                    'target_sim': None,
                    'num_relations': 1,
                    'tooltip': None
                })),
        'subject_passes_membership_criteria':
        OptionalTunable(
            TunableVariant(
                description=
                '\n            If enabled, require the tuned "subject" to either pass this\n            associated Club\'s membership criteria or not pass the membership\n            criteria.\n            ',
                locked_args={
                    'Passes Criteria': True,
                    'Does Not Pass Criteria': False
                })),
        'subject_can_join_more_clubs':
        OptionalTunable(
            TunableVariant(
                description=
                '\n            If enabled, require the tuned "subject" to be allowed to join more\n            Clubs or not.\n            \n            The maximum number of Clubs per Sim is set in\n            club_tuning.ClubTunables in the "MAX_CLUBS_PER_SIM" tunable.\n            ',
                locked_args={
                    'Can Join More Clubs': True,
                    'Cannot Join More Clubs': False
                })),
        'required_sim_count':
        OptionalTunable(
            description=
            '\n            If enabled then this test will only pass if the group has a number \n            of members that passes the tuned threshold.\n            ',
            tunable=TunableThreshold(
                description=
                '\n                The member requirement for this test to pass.\n                '
            )),
        'affordance_rule':
        OptionalTunable(
            description=
            '\n            If set, then the affordance being tested (should one exist) must\n            satisfy this rule requirement.\n            ',
            tunable=TunableVariant(
                description=
                '\n                The rule requirement that the affordance must satisfy.\n                ',
                locked_args={
                    'is_encouraged': AFFORDANCE_RULE_ENCOURAGED,
                    'is_discouraged': AFFORDANCE_RULE_DISCOURAGED,
                    'is_not_encouraged': AFFORDANCE_RULE_NOT_ENCOURAGED,
                    'is_not_discouraged': AFFORDANCE_RULE_NOT_DISCOURAGED
                },
                default='is_encouraged')),
        'pass_if_any_clubs_pass':
        Tunable(
            description=
            '\n            If checked then this test will pass if any of the clubs match the\n            requirements otherwise we require all clubs to meet the\n            requirements.\n            ',
            tunable_type=bool,
            default=False)
    }
    test_events = (TestEvent.ClubMemberAdded, TestEvent.LeaderAssigned)

    def get_expected_args(self):
        expected_args = {'test_subjects': self.subject}
        if self.club == self.CLUB_USE_ASSOCIATED:
            expected_args['associated_clubs'] = ParticipantType.AssociatedClub
        elif self.club == self.CLUB_FROM_EVENT_DATA:
            expected_args[
                'associated_clubs'] = event_testing.test_constants.FROM_EVENT_DATA
        if self.require_common_club is not None:
            expected_args['common_test_subjects'] = self.require_common_club
        if self.affordance_rule is not None:
            expected_args['affordance'] = ParticipantType.Affordance
            expected_args['affordance_targets'] = ParticipantType.Object
        return expected_args

    def _club_test_enabled(self):
        if self.recent_member_status is not None or (
                self.room_for_new_members is not None or
            (self.invite_only is not None or
             (self.subject_passes_membership_criteria is not None or
              (self.subject_relationship_with_leader is not None
               or self.required_sim_count)))) or self.affordance_rule:
            return True
        return False

    def _club_status_test(self, subject, clubs):
        if self.club_status is None:
            return TestResult.TRUE
        if not clubs:
            if self.club_status == NOT_MEMBER or self.club_status == NOT_LEADER:
                return TestResult.TRUE
            return TestResult(
                False,
                'Subject {} is not a member or leader of any clubs',
                subject,
                tooltip=self.tooltip)
        passing_clubs = 0
        for club in clubs:
            in_members_list = subject in club.members
            is_leader = subject is club.leader
            if self.club_status == MEMBER and not in_members_list:
                if self.pass_if_any_clubs_pass:
                    continue
                return TestResult(
                    False,
                    'Subject {} not a member of Club {} but should be.',
                    subject,
                    club,
                    tooltip=self.tooltip)
            if self.club_status == NOT_MEMBER and in_members_list:
                if self.pass_if_any_clubs_pass:
                    continue
                return TestResult(
                    False,
                    "Subject {} is a member of Club {} but shouldn't be.",
                    subject,
                    club,
                    tooltip=self.tooltip)
            if self.club_status == LEADER and not is_leader:
                if self.pass_if_any_clubs_pass:
                    continue
                return TestResult(
                    False,
                    'Subject {} is not the leader of Club {} but should be.',
                    subject,
                    club,
                    tooltip=self.tooltip)
            if self.club_status == NOT_LEADER and is_leader:
                if self.pass_if_any_clubs_pass:
                    continue
                return TestResult(
                    False,
                    "Subject {} is the leader of Club {} but shouldn't be.",
                    subject,
                    club,
                    tooltip=self.tooltip)
            passing_clubs += 1
        if self.pass_if_any_clubs_pass and passing_clubs == 0:
            return TestResult(
                False,
                'Subject {} not in any clubs that pass the criteria.',
                subject,
                tooltip=self.tooltip)
        return TestResult.TRUE

    def _test_club(self, subject, club, affordance_data=None):
        club_service = services.get_club_service()
        if self.recent_member_status is not None:
            is_recent_member = club.is_recent_member(subject)
            if self.recent_member_status != is_recent_member:
                return TestResult(
                    False,
                    "Subject {}'s recent member status in {} is {}, but the required status is {}",
                    subject,
                    club,
                    is_recent_member,
                    self.recent_member_status,
                    tooltip=self.tooltip)
        if self.room_for_new_members is not None:
            club_has_room = len(club.members) < club.get_member_cap()
            if self.room_for_new_members and not club_has_room:
                return TestResult(
                    False,
                    'Club {} has no room for new members but is required to.',
                    club,
                    tooltip=self.tooltip)
            if not self.room_for_new_members and club_has_room:
                return TestResult(
                    False,
                    'Club {} has room for new members but is required not to.',
                    club,
                    tooltip=self.tooltip)
        if self.invite_only is not None and club.invite_only != self.invite_only:
            return TestResult(
                False,
                "Club {}'s invite_only status is expected to be {} but isn't.",
                club,
                self.invite_only,
                tooltip=self.tooltip)
        if self.subject_passes_membership_criteria is not None:
            subject_result = club.validate_sim_info(subject)
            if subject_result and not self.subject_passes_membership_criteria:
                return TestResult(
                    False,
                    'Subject {} passes the membership criteria for Club {} but is required not to.',
                    subject,
                    club,
                    tooltip=self.tooltip)
            if not subject_result and self.subject_passes_membership_criteria:
                return TestResult(
                    False,
                    'Subject {} does not pass the membership criteria for Club {} but is required to.',
                    subject,
                    club,
                    tooltip=self.tooltip)
        if self.subject_relationship_with_leader is not None:
            if subject is club.leader:
                return TestResult(
                    False,
                    'Subject {} requires relationship with the leader, but is the leader of Club {}',
                    subject,
                    club,
                    tooltip=self.tooltip)
            relationship_test_result = self.subject_relationship_with_leader(
                source_sims=(subject, ), target_sims=(club.leader, ))
            if not relationship_test_result:
                return relationship_test_result
        if self.required_sim_count is not None and not self.required_sim_count.compare(
                len(club.members)):
            return TestResult(
                False,
                "The club {} doesn't meet the required sim count of {}",
                club,
                self.required_sim_count,
                tooltip=self.tooltip)
        if self.affordance_rule is not None:
            if club_service is None:
                return TestResult(
                    False,
                    'Affordance {} does not satisfy the required Club rules requirements. There is no club service.',
                    affordance_data.affordance,
                    tooltip=self.tooltip)
            (
                rule_status, _
            ) = club_service.get_interaction_encouragement_status_and_rules_for_sim_info(
                subject, affordance_data)
            if self.affordance_rule == self.AFFORDANCE_RULE_ENCOURAGED:
                if rule_status != ClubRuleEncouragementStatus.ENCOURAGED:
                    return TestResult(
                        False,
                        'Affordance {} does not satisfy the required Club rules requirements',
                        affordance_data.affordance,
                        tooltip=self.tooltip)
            elif self.affordance_rule == self.AFFORDANCE_RULE_DISCOURAGED:
                if rule_status != ClubRuleEncouragementStatus.DISCOURAGED:
                    return TestResult(
                        False,
                        'Affordance {} does not satisfy the required Club rules requirements',
                        affordance_data.affordance,
                        tooltip=self.tooltip)
            elif self.affordance_rule == self.AFFORDANCE_RULE_NOT_ENCOURAGED:
                if rule_status == ClubRuleEncouragementStatus.ENCOURAGED:
                    return TestResult(
                        False,
                        'Affordance {} does not satisfy the required Club rules requirements',
                        affordance_data.affordance,
                        tooltip=self.tooltip)
            elif self.affordance_rule == self.AFFORDANCE_RULE_NOT_DISCOURAGED and rule_status == ClubRuleEncouragementStatus.DISCOURAGED:
                return TestResult(
                    False,
                    'Affordance {} does not satisfy the required Club rules requirements',
                    affordance_data.affordance,
                    tooltip=self.tooltip)
        return TestResult.TRUE

    def __call__(self,
                 test_subjects=(),
                 associated_clubs=(),
                 common_test_subjects=(),
                 affordance=None,
                 affordance_targets=()):
        club_service = services.get_club_service()

        def get_clubs_for_subject(subject):
            if self.club == self.CLUB_USE_ANY or associated_clubs is None:
                if club_service is not None:
                    return tuple(club_service.get_clubs_for_sim_info(subject))
                return ()
            elif self.club == self.CLUB_USE_ASSOCIATED or self.club == self.CLUB_FROM_EVENT_DATA:
                return associated_clubs
            return ()

        for subject in test_subjects:
            if self.subject_can_join_more_clubs is not None:
                can_join_new_club = club_service.can_sim_info_join_more_clubs(
                    subject) if club_service is not None else False
                if can_join_new_club and not self.subject_can_join_more_clubs:
                    return TestResult(
                        False,
                        "Subject {} is allowed to join more Clubs but shouldn't be.",
                        subject,
                        tooltip=self.tooltip)
                if not can_join_new_club and self.subject_can_join_more_clubs:
                    return TestResult(
                        False,
                        'Subject {} is not allowed to join more Clubs but should be.',
                        subject,
                        tooltip=self.tooltip)
            clubs = get_clubs_for_subject(subject)
            result = self._club_status_test(subject, clubs)
            if not result:
                return result
            if self._club_test_enabled():
                if common_test_subjects:
                    common_test_clubs = set(
                        club for s in common_test_subjects
                        for club in get_clubs_for_subject(s))
                    if not set(clubs) & common_test_clubs:
                        return TestResult(
                            False,
                            "Subject {} and {} don't share an appropriate common Club",
                            subject,
                            common_test_subjects,
                            tooltip=self.tooltip)
                affordance_data = self._AffordanceData(
                    affordance, next(iter(affordance_targets), None))
                if not any(
                        self._test_club(
                            subject, club, affordance_data=affordance_data)
                        for club in clubs):
                    return TestResult(False,
                                      'Subject {} fails Club test for {}',
                                      subject,
                                      clubs,
                                      tooltip=self.tooltip)
        return TestResult.TRUE
示例#19
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
示例#20
0
class UniversityMajor(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(Types.UNIVERSITY_MAJOR)):
    INSTANCE_TUNABLES = {'courses': TunableList(description='\n            List of courses, in order, for this major\n            ', tunable=UniversityCourseData.TunableReference(), minlength=1), 'acceptance_score': TunableTuple(description='\n            Score requirement to be accepted in this major as prestige degree.\n            ', score=TunableMultiplier.TunableFactory(description='\n                Define the base score and multiplier to calculate acceptance\n                score of a Sim.\n                '), score_threshold=TunableThreshold(description='\n                The threshold to perform against the score to see if a Sim \n                can be accepted in this major.\n                ')), 'display_name': TunableLocalizedString(description="\n            The major's name.\n            ", tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'display_description': TunableLocalizedString(description="\n            The major's description.\n            ", tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'icons': TunableTuple(description='\n            Display icons for this major.\n            ', icon=TunableIcon(description="\n                The major's icon.\n                "), icon_prestige=TunableIcon(description="\n                The major's prestige icon.\n                "), icon_high_res=TunableIcon(description="\n                The major's high resolution icon.\n                "), icon_prestige_high_res=TunableIcon(description="\n                The major's prestige high resolution icon.\n                "), export_class_name='UniversityMajorIconTuple', tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'major_benefit_map': TunableMapping(description='\n            University specific major benefit description. Each university can \n            have its own description defined for this University Major.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableLocalizedString(description='\n                Major benefit description.\n                '), tuple_name='UniversityMajorBenefitMapping', tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'graduation_reward': TunableMapping(description='\n            Loot on graduation at each university for each GPA threshold\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableList(description='\n                Loot for each GPA range (lower bound inclusive, upper bound\n                exclusive.\n                ', tunable=TunableTuple(gpa_range=TunableInterval(description='\n                        GPA range to receive this loot.\n                        Lower bound inclusive, upper bound exclusive.\n                        ', tunable_type=float, default_lower=0, default_upper=10), loot=TunableList(tunable=LootActions.TunableReference(description='\n                            The loot action applied.\n                            ', pack_safe=True))))), 'career_tracks': TunableList(description='\n            List of career tracks for which the UI will indicate this major\n            will provide benefit.  Is not used to actually provide said benefit.\n            ', tunable=TunableReference(description='\n                These are the career tracks that will benefit from this major.\n                ', manager=services.get_instance_manager(sims4.resources.Types.CAREER_TRACK), pack_safe=True), tuning_group=GroupNames.UI, export_modes=ExportModes.ClientBinary, unique_entries=True)}

    @classmethod
    def graduate(cls, sim_info, university, gpa):
        resolver = SingleSimResolver(sim_info)
        if university in cls.graduation_reward:
            for grad_reward in cls.graduation_reward[university]:
                if grad_reward.gpa_range.lower_bound <= gpa < grad_reward.gpa_range.upper_bound:
                    for loot_action in grad_reward.loot:
                        loot_action.apply_to_resolver(resolver)

    @classmethod
    def get_sim_acceptance_score(cls, sim_info):
        resolver = SingleSimResolver(sim_info)
        return cls.acceptance_score.score.get_multiplier(resolver)

    @classmethod
    def can_sim_be_accepted(cls, sim_info):
        sim_score = cls.get_sim_acceptance_score(sim_info)
        return cls.acceptance_score.score_threshold.compare(sim_score)
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
示例#22
0
    class GPATest(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {'actor': TunableEnumEntry(description='\n                The actor whose GPA will be used. Must have a GPA greater than \n                or equal to the tuned GPA to pass the test.\n                ', tunable_type=ParticipantTypeSingleSim, default=ParticipantTypeSingleSim.Actor), 'gpa': TunableThreshold(description='\n                The GPA to compare against.\n                ', value=Tunable(description='\n                    The value of the threshold that the gpa is compared\n                    against.\n                    ', tunable_type=float, default=3.5))}

        def get_expected_args(self):
            return {'actors': self.actor}

        def test(self, tooltip=None, actors=()):
            actor = next(iter(actors), None)
            if actor is None:
                return TestResult(False, "Actor {} doesn't exist.", self.actor, tooltip=tooltip)
            degree_tracker = actor.sim_info.degree_tracker
            if degree_tracker is None:
                return TestResult(False, "Actor {} doesn't have degree tracker.", actor, tooltip=tooltip)
            gpa = degree_tracker.get_gpa()
            if gpa is not None and self.gpa.compare(gpa):
                return TestResult.TRUE
            return TestResult(False, 'Actor {} has a GPA of {}, but needs to have a GPA of {} or higher.', actor, gpa, self.gpa, tooltip=tooltip)
class ObstacleCourseSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'coach_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            Job and Role State for the coach Sim. Pre-populated as\n            the actor of the Situation.\n            ',
            tuning_group=GroupNames.ROLES),
        'athlete_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            Job and Role State for the athlete. Pre-populated as the\n            target of the Situation.\n            ',
            tuning_group=GroupNames.ROLES),
        'run_course_state':
        RunCourseState.TunableFactory(tuning_group=GroupNames.STATE),
        'obstacle_tags':
        TunableTags(
            description=
            '\n            Tags to use when searching for obstacle course objects.\n            ',
            filter_prefixes=('Func_PetObstacleCourse', ),
            minlength=1),
        'setup_obstacle_state_value':
        ObjectStateValue.TunableReference(
            description=
            '\n            The state to setup obstacles before we run the course.\n            '
        ),
        'teardown_obstacle_state_value':
        ObjectStateValue.TunableReference(
            description=
            '\n            The state to teardown obstacles after we run the course or when the\n            situation ends.\n            '
        ),
        'failure_commodity':
        Commodity.TunableReference(
            description=
            '\n            The commodity we use to track how many times the athlete has failed\n            to overcome an obstacle.\n            '
        ),
        'obstacles_required':
        TunableRange(
            description=
            '\n            The number of obstacles required for the situation to be available. \n            If the obstacles that the pet can route to drops below this number,\n            the situation is destroyed.\n            ',
            tunable_type=int,
            default=4,
            minimum=1),
        'unfinished_notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            The dialog for when the situation ends prematurely or the dog never\n            finishes the course.\n            Token 0: Athlete\n            Token 1: Coach\n            Token 2: Time\n            ',
            tuning_group=GroupNames.UI),
        'finish_notifications':
        TunableList(
            description=
            '\n            A list of thresholds and notifications to play given the outcome of\n            the course. We run through the thresholds until one passes, and\n            play the corresponding notification.\n            ',
            tuning_group=GroupNames.UI,
            tunable=TunableTuple(
                description=
                '\n                A threshold and notification to play if the threshold passes.\n                ',
                threshold=TunableThreshold(
                    description=
                    '\n                    A threshold to compare the number of failures from the\n                    failure commodity when the course is finished.\n                    '
                ),
                notification=UiDialogNotification.TunableFactory(
                    description=
                    '\n                    Notification to play when the situation ends.\n                    Token 0: Athlete\n                    Token 1: Coach\n                    Token 2: Failure Count\n                    Token 3: Time\n                    '
                )))
    }

    @classmethod
    def _states(cls):
        return (SituationStateData(0, WaitForSimJobsState),
                SituationStateData(1,
                                   RunCourseState,
                                   factory=cls.run_course_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.coach_job_and_role_state.job,
                 cls.coach_job_and_role_state.role_state),
                (cls.athlete_job_and_role_state.job,
                 cls.athlete_job_and_role_state.role_state)]

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def get_prepopulated_job_for_sims(cls, sim, target_sim_id=None):
        prepopulate = [(sim.id, cls.coach_job_and_role_state.job.guid64)]
        if target_sim_id is not None:
            prepopulate.append(
                (target_sim_id, cls.athlete_job_and_role_state.job.guid64))
        return prepopulate

    @classmethod
    def get_obstacles(cls):
        object_manager = services.object_manager()
        found_objects = set()
        for tag in cls.obstacle_tags:
            found_objects.update(
                object_manager.get_objects_matching_tags({tag}))
        return found_objects

    @classmethod
    def is_situation_available(cls, *args, **kwargs):
        obstacles = cls.get_obstacles()
        if len(obstacles) < cls.obstacles_required:
            return TestResult(False, 'Not enough obstacles.')
        return super().is_situation_available(*args, **kwargs)

    @classproperty
    def situation_serialization_option(cls):
        return situations.situation_types.SituationSerializationOption.LOT

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        reader = self._seed.custom_init_params_reader
        if reader is not None:
            obstacles = self.get_obstacles()
            if not obstacles:
                self._self_destruct()
            self._obstacle_ids = {obstacle.id for obstacle in obstacles}
            self._course_start_time = DateAndTime(
                reader.read_uint64(OBSTACLE_COURSE_START_TIME_TOKEN,
                                   services.time_service().sim_now))
            self._course_end_time = DateAndTime(
                reader.read_uint64(OBSTACLE_COURSE_END_TIME_TOKEN,
                                   services.time_service().sim_now))
        else:
            self._obstacle_ids = set()
            self._course_start_time = None
            self._course_end_time = None
        self._course_progress = ObstacleCourseProgress.NOT_STARTED

    @property
    def course_progress(self):
        return self._course_progress

    @property
    def obstacle_ids(self):
        return self._obstacle_ids

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        if self._course_start_time is not None:
            writer.write_uint64(OBSTACLE_COURSE_START_TIME_TOKEN,
                                int(self._course_start_time))
        if self._course_end_time is not None:
            writer.write_uint64(OBSTACLE_COURSE_END_TIME_TOKEN,
                                int(self._course_end_time))

    def start_situation(self):
        super().start_situation()
        self._register_obstacle_course_events()
        self._change_state(WaitForSimJobsState())

    def _on_remove_sim_from_situation(self, sim):
        super()._on_remove_sim_from_situation(sim)
        self._self_destruct()

    def _on_add_sim_to_situation(self, sim, job_type, *args, **kwargs):
        super()._on_add_sim_to_situation(sim, job_type, *args, **kwargs)
        if self.get_coach() is not None and self.get_athlete() is not None:
            object_manager = services.object_manager()
            obstacles = {
                object_manager.get(obstacle_id)
                for obstacle_id in self._obstacle_ids
            }
            sim_info_manager = services.sim_info_manager()
            users = sim_info_manager.instanced_sims_gen()
            for user in users:
                if user in self._situation_sims:
                    continue
                for interaction in user.get_all_running_and_queued_interactions(
                ):
                    target = interaction.target
                    target = target.part_owner if target is not None and target.is_part else target
                    if target is not None and target in obstacles:
                        interaction.cancel(
                            FinishingType.SITUATIONS,
                            cancel_reason_msg='Obstacle Course Starting')
            self._change_state(self.run_course_state())

    def _register_obstacle_course_events(self):
        services.get_event_manager().register_single_event(
            self, TestEvent.ObjectDestroyed)
        services.get_event_manager().register_single_event(
            self, TestEvent.OnExitBuildBuy)

    def _unregister_obstacle_course_events(self):
        services.get_event_manager().unregister_single_event(
            self, TestEvent.ObjectDestroyed)
        services.get_event_manager().unregister_single_event(
            self, TestEvent.OnExitBuildBuy)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event == TestEvent.ObjectDestroyed:
            destroyed_object = resolver.get_resolved_arg('obj')
            if destroyed_object.id in self._obstacle_ids:
                self._obstacle_ids.remove(destroyed_object.id)
                if len(self._obstacle_ids) < self.obstacles_required:
                    self._self_destruct()
        elif event == TestEvent.OnExitBuildBuy:
            self.validate_obstacle_course()

    def on_remove(self):
        coach = self.get_coach()
        athlete = self.get_athlete()
        if coach is not None and athlete is not None:
            if self.course_progress > ObstacleCourseProgress.NOT_STARTED and self.course_progress < ObstacleCourseProgress.FINISHED:
                course_end_time = services.time_service().sim_now
                course_time_span = course_end_time - self._course_start_time
                unfinished_dialog = self.unfinished_notification(coach)
                unfinished_dialog.show_dialog(
                    additional_tokens=(athlete, coach, course_time_span))
            athlete.commodity_tracker.remove_statistic(self.failure_commodity)
        self.teardown_obstacle_course()
        self._unregister_obstacle_course_events()
        super().on_remove()

    def start_course(self):
        self._course_progress = ObstacleCourseProgress.RUNNING
        self._course_start_time = services.time_service(
        ).sim_now if self._course_start_time is None else self._course_start_time

    def continue_course(self):
        self._change_state(self.run_course_state())

    def finish_course(self):
        self._course_end_time = services.time_service().sim_now
        self._course_progress = ObstacleCourseProgress.FINISHED
        self._change_state(self.run_course_state())

    def finish_situation(self):
        course_time_span = self._course_end_time - self._course_start_time
        athlete = self.get_athlete()
        coach = self.get_coach()
        failures = athlete.commodity_tracker.get_value(self.failure_commodity)
        for threshold_notification in self.finish_notifications:
            if threshold_notification.threshold.compare(failures):
                dialog = threshold_notification.notification(coach)
                dialog.show_dialog(additional_tokens=(athlete, coach, failures,
                                                      course_time_span))
                break
        else:
            logger.error(
                "Obstacle Course Situation doesn't have a threshold, notification for failure count of {}",
                failures)
        self._self_destruct()

    def setup_obstacle_course(self):
        obstacles = self.get_obstacles()
        if len(obstacles) < self.obstacles_required:
            self._self_destruct()
        self._obstacle_ids = {obstacle.id for obstacle in obstacles}

    def validate_obstacle_course(self):
        athlete = self.get_athlete()
        if athlete is None:
            self._self_destruct()
            return
        all_obstacles = self.get_obstacles()
        if len(all_obstacles) < self.obstacles_required:
            self._self_destruct()
            return
        valid_obstacles = set()
        for obstacle in all_obstacles:
            currentState = obstacle.get_state(
                self.setup_obstacle_state_value.state)
            if obstacle.is_connected(athlete):
                valid_obstacles.add(obstacle)
                if currentState == self.teardown_obstacle_state_value:
                    obstacle.set_state(self.setup_obstacle_state_value.state,
                                       self.setup_obstacle_state_value,
                                       immediate=True)
                    if currentState == self.setup_obstacle_state_value:
                        obstacle.set_state(
                            self.setup_obstacle_state_value.state,
                            self.teardown_obstacle_state_value,
                            immediate=True)
            elif currentState == self.setup_obstacle_state_value:
                obstacle.set_state(self.setup_obstacle_state_value.state,
                                   self.teardown_obstacle_state_value,
                                   immediate=True)
        if len(valid_obstacles) < self.obstacles_required:
            self._self_destruct()
        else:
            self._obstacle_ids = {obstacle.id for obstacle in valid_obstacles}

    def teardown_obstacle_course(self):
        obstacles = self.get_obstacles()
        for obstacle in obstacles:
            obstacle.set_state(self.teardown_obstacle_state_value.state,
                               self.teardown_obstacle_state_value,
                               immediate=True)

    def get_coach(self):
        return next(
            iter(self.all_sims_in_job_gen(self.coach_job_and_role_state.job)),
            None)

    def get_athlete(self):
        return next(
            iter(self.all_sims_in_job_gen(
                self.athlete_job_and_role_state.job)), None)
示例#24
0
    class AcceptedPrestigeDegreeCountTest(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {'actor': TunableEnumEntry(description='\n                The actor whose degree will be used.\n                ', tunable_type=ParticipantTypeSingleSim, default=ParticipantTypeSingleSim.Actor), 'number_to_test': TunableThreshold(description='\n                The number of accepted prestige degrees to pass this test.\n                ')}

        def get_expected_args(self):
            return {'actors': self.actor}

        def test(self, tooltip=None, actors=()):
            actor = next(iter(actors), None)
            if actor is None:
                return TestResult(False, "Actor {} doesn't exist.", self.actor, tooltip=tooltip)
            degree_tracker = actor.sim_info.degree_tracker
            if degree_tracker is None:
                return TestResult(False, "Actor {} doesn't have degree tracker.", actor, tooltip=tooltip)
            acc_prestige_degrees = degree_tracker.get_accepted_prestige_degrees()
            num_acc_prestige_degrees = 0
            for degrees in acc_prestige_degrees.values():
                num_acc_prestige_degrees += len(degrees)
            if not self.number_to_test.compare(num_acc_prestige_degrees):
                return TestResult(False, '{} of accepted prestige degrees, failed threshold {}:{}.', num_acc_prestige_degrees, self.number_to_test.comparison, self.number_to_test.value, tooltip=tooltip)
            return TestResult.TRUE
 def __init__(self, description='Apply an operation to a statistic.', **kwargs):
     super().__init__(who=TunableEnumEntry(ParticipantType, ParticipantType.Actor, description='Who or what to apply this test to'), stat=TunableReference(services.statistic_manager(), description='The commodity we are gaining.'), threshold=TunableThreshold(description='A commodity value and comparison that defines the exit condition hits (or hit commodity.maximum).'), absolute=Tunable(bool, True, needs_tuning=True, description="True = treat the threshold value as an absolute commodity value.  Otherwise, it is relative to the Sim's start value."), **kwargs)
class ObjectRelationshipCondition(HasTunableFactory, AutoFactoryInit, Condition):
    __qualname__ = 'ObjectRelationshipCondition'
    FACTORY_TUNABLES = {'description': '\n            A condition that is satisfied when a Sim reaches a specific object\n            relationship threshold.\n            ', 'sim': TunableEnumEntry(description="\n            The Sim whose object relationship we're checking.\n            ", tunable_type=ParticipantType, default=ParticipantType.Actor), 'object': TunableEnumEntry(description="\n            The object whose object relationship we're checking.\n            ", tunable_type=ParticipantType, default=ParticipantType.Object), 'threshold': TunableThreshold(description='\n            The relationship threshold that will trigger this condition.\n            ')}

    def __init__(self, interaction=None, **kwargs):
        super().__init__(**kwargs)
        self._interaction = interaction

    def __str__(self):
        return 'Object Relationship: {} {}'.format(self.threshold.comparison, self.threshold.value)

    def _on_relationship_changed(self):
        sim = self._owner.get_participant(self.sim)
        obj = self._owner.get_participant(self.object)
        relationship_value = obj.objectrelationship_component.get_relationship_value(sim.id)
        if relationship_value is not None and self.threshold.compare(relationship_value):
            self._satisfy()
            if self._interaction:
                self._interaction._send_progress_bar_update_msg(1, 0, self._owner)
        if not self._satisfied and self._interaction:
            initial_value = obj.objectrelationship_component.get_relationship_initial_value()
            denominator = self.threshold.value - initial_value
            if denominator != 0:
                rate_change = relationship_value - initial_value/self.threshold.value - initial_value
                self._owner._send_progress_bar_update_msg(rate_change/100, 0, self._owner)

    def attach_to_owner(self, owner, callback):
        self.si_callback = callback
        self._owner = owner
        sim = owner.get_participant(self.sim)
        obj = self._owner.get_participant(self.object)
        if sim is None:
            logger.error('Trying to add a condition for {} to test object relationship, but the                           ParticipantType {} tuned for Sim is not valid.', self._owner, sim, owner='tastle')
        elif obj is None:
            logger.error('Trying to add a condition for {} to test object relationship, but the                           ParticipantType {} tuned for Object is not valid.', self._owner, obj, owner='tastle')
        elif obj.objectrelationship_component is None:
            logger.error('Trying to add a condition on interaction {} to test object relationship, but                           {} has no object relationship component.', self._owner, obj, owner='tastle')
        else:
            obj.objectrelationship_component.add_relationship_changed_callback_for_sim_id(sim.sim_id, self._on_relationship_changed)
            self._on_relationship_changed()
        return (None, None)

    def detach_from_owner(self, owner, exiting=False):
        sim = owner.get_participant(self.sim)
        obj = self._owner.get_participant(self.object)
        if sim is None or obj is None:
            return
        if obj.objectrelationship_component is not None:
            obj.objectrelationship_component.remove_relationship_changed_callback_for_sim_id(sim.sim_id, self._on_relationship_changed)
示例#27
0
 def location_tests(is_outside=True, is_natural_ground=True, is_in_slot=True, is_venue_type=True, is_on_active_lot=True, in_common_area=True, is_fire_allowed=True, is_on_level=True, has_terrain_tag=True, valid_surface_types=True):
     locked_args = {}
     if not is_outside:
         locked_args['is_outside'] = None
     if not is_natural_ground:
         locked_args['is_natural_ground'] = None
     if not is_in_slot:
         locked_args['is_in_slot'] = None
     if not is_venue_type:
         locked_args['is_venue_type'] = None
     if not is_on_active_lot:
         locked_args['is_on_active_lot'] = None
     if not in_common_area:
         locked_args['in_common_area'] = None
     if not is_fire_allowed:
         locked_args['is_fire_allowed'] = None
     if not is_on_level:
         locked_args['is_on_level'] = None
     if not has_terrain_tag:
         locked_args['has_terrain_tag'] = None
     if not valid_surface_types:
         locked_args['valid_surface_types'] = None
     return TunableTuple(is_outside=OptionalTunable(description='\n                If checked, will verify if the subject of the test is outside \n                (no roof over its head) \n                If unchecked, will verify the subject of the test is not \n                outside.\n                ', disabled_name="Don't_Test", tunable=Tunable(bool, True)), has_terrain_tag=OptionalTunable(description='\n                If checked, will verify the subject of the test is currently on\n                the tuned terrain tag.\n                ', disabled_name="Don't_Test", tunable=TunableTuple(description=',\n                    A set of terrain tags required for this test to pass.\n                    ', terrain_tags=TunableEnumSet(description='\n                        A set of terrain tags. Only one of these tags needs to be\n                        present at this location. Although it is not tunable, there\n                        is a threshold weight underneath which a terrain tag will\n                        not appear to be present.\n                        ', enum_type=TerrainTag, enum_default=TerrainTag.INVALID), test_floor_tiles=Tunable(description="\n                        If checked, floor tiles will be tested. Otherwise, \n                        it'll only check the terrain and will ignore the \n                        floor tiles on the terrain.\n                        ", tunable_type=bool, default=False), negate=Tunable(description='\n                        If checked, the test will be inverted. In other words,\n                        the test will fail if at least one tag is detected at\n                        this location.\n                        ', tunable_type=bool, default=False))), is_natural_ground=OptionalTunable(description='\n                If checked, will verify the subject of the test is on natural \n                ground (no floor tiles are under him).\n                Otherwise, will verify the subject of the test is not on \n                natural ground.\n                ', disabled_name="Don't_Test", tunable=Tunable(bool, True)), is_in_slot=OptionalTunable(description='\n                If enabled will test if the object is attacked/deattached to\n                any of possible tuned slots.\n                If you tune a slot type set the test will test if the object \n                is slotted or not slotted into into any of those types. \n                ', disabled_name="Don't_Test", tunable=TunableTuple(description='\n                    Test if an object is current slotted in any of a possible\n                    list of slot types.\n                    Empty slot type set is allowed for testing for slotted or\n                    not slotted only.\n                    ', slot_test_type=TunableVariant(description='\n                        Strategy to test the slots:\n                        Any Slot - is the object in any slot\n                        Surface Slot - is object is in a surface slot\n                        Specific Slot - is the object in specific list of slots\n                        ', any_slot=SlotTestType.TunableFactory(), surface_slot=SurfaceSlotTest.TunableFactory(), specific_slot=SpecificSlotTest.TunableFactory(), default='any_slot'))), is_venue_type=OptionalTunable(description='\n                If checked, will verify if the subject is at a venue of the\n                specified type.\n                ', disabled_name="Don't_Test", tunable=TunableTuple(description='\n                    Venue type required for this test to pass.\n                    ', venue_type=TunablePackSafeReference(description='\n                        Venue type to test against.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.VENUE)), use_source_venue=Tunable(description='\n                        If enabled, the test will test the source venue instead of the active\n                        venue.  For example, the Community Lot instead of the active Marketplace.\n                        Testing the active venue is the default.\n                        ', tunable_type=bool, default=False), negate=Tunable(description='\n                        If enabled, the test will return true if the subject\n                        IS NOT at a venue of the specified type.\n                        ', tunable_type=bool, default=False))), is_on_active_lot=OptionalTunable(description='\n                If disabled the test will not be used.\n                If enabled and checked, the test will pass if the subject is\n                on the active lot. (their center is within the lot bounds)\n                If enabled and not checked, the test will pass if the subject is \n                outside of the active lot.\n                \n                For example, Ask To Leave is tuned with this enabled and checked\n                for the TargetSim. You can only ask someone to leave if they\n                are actually on the active lot, but not if they are wandering\n                around in the open streets.\n                ', disabled_name="Don't_Test", enabled_name='Is_or_is_not_on_active_lot', tunable=TunableTuple(is_or_is_not_on_active_lot=Tunable(description='\n                        If checked then the test will pass if the subject is on\n                        the active lot.\n                        ', tunable_type=bool, default=True), tolerance=TunableVariant(explicit=Tunable(description='\n                            The tolerance from the edge of the lot that the\n                            location test will use in order to determine if the\n                            test target is considered on lot or not.\n                            ', tunable_type=int, default=0), use_default_tolerance=UseDefaultOfflotToleranceFactory(description='\n                            Use the default tuned global offlot tolerance tuned\n                            in objects.components.statistic_component.Default Off Lot.\n                            '), default='explicit'), include_spawn_point=Tunable(description="\n                        If set to true, we will consider the lot's spawn point as part of the active lot.\n                        ", tunable_type=bool, default=False))), in_common_area=OptionalTunable(description='\n                If checked, will verify the subject is in the common area\n                of an apartment.  If unchecked will verify the subject is not.\n                ', disabled_name="Don't_Test", tunable=Tunable(tunable_type=bool, default=True)), is_fire_allowed=OptionalTunable(description="\n                If checked, will verify if fire is possible at the subject's position. \n                If unchecked, will pass if fire is not possible.\n                If not enabled, doesn't care either way.\n                ", disabled_name="Don't_Test", tunable=Tunable(tunable_type=bool, default=True)), is_on_level=OptionalTunable(description="\n                If enabled, we check the participant's current level against\n                the tuned threshold.  In the case of sims in pools, the effective\n                level will be that of the surface of the pool, not the bottom.\n                ", disabled_name="Don't_Test", tunable=TunableThreshold(value=Tunable(int, 0))), valid_surface_types=OptionalTunable(description='\n                If enabled, we will test the surface type of the subject\n                against prohibited or required surface types.\n                ', disabled_name="Don't_Test", enabled_name='Test_Surface_Types', tunable=TunableWhiteBlackList(description='    \n                    Required and Prohibited Surface Types. \n                    ', tunable=TunableEnumEntry(description='\n                        Surface Type the object is placed on.\n                        ', tunable_type=SurfaceType, default=SurfaceType.SURFACETYPE_WORLD, invalid_enums=(SurfaceType.SURFACETYPE_UNKNOWN,)))), locked_args=locked_args)
class _GreetedPlayerVisitingNPCState(CommonSituationState):
    FACTORY_TUNABLES = {'scolding_interactions': TunableInteractionOfInterest(description='\n                 The interaction, when run increases your scold count.\n                 '), 'scolding_notification': UiDialogNotification.TunableFactory(description='\n            The notification to display after scolding a greeted player.\n            '), 'inappropriate_behavior_threshold': TunableThreshold(description='\n            Threshold for times a Sim may be scolded for inappropriate behavior.\n            When leaving this threshold, they will be sent away. \n            '), 'send_away_notification': UiDialogNotification.TunableFactory(description='\n            Notification to be triggered when sending away the sim.\n            '), 'send_away_inappropriate_sim_interaction': TunableReference(description='\n            The affordance that the reacting NPC will run to tell \n            the inappropriate Sim to leave. \n            ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))}

    def __init__(self, scolding_interactions, scolding_notification, inappropriate_behavior_threshold, send_away_notification, send_away_inappropriate_sim_interaction, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.scolding_interactions = scolding_interactions
        self.scolding_notification = scolding_notification
        self.inappropriate_behavior_threshold = inappropriate_behavior_threshold
        self.send_away_notification = send_away_notification
        self.send_away_inappropriate_sim_interaction = send_away_inappropriate_sim_interaction
        self._scold_count = 0

    def on_activate(self, reader=None):
        super().on_activate(reader)
        if reader is None:
            self._scold_count = 0
        else:
            self._scold_count = reader.read_uint32(SCOLD_COUNT_TOKEN, 0)
        for custom_key in self.scolding_interactions.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionComplete, custom_key)

    def save_state(self, writer):
        super().save_state(writer)
        writer.write_uint32(SCOLD_COUNT_TOKEN, self._scold_count)

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.InteractionComplete and resolver(self.scolding_interactions):
            self._handle_scolding_interaction(sim_info, event, resolver)

    def _handle_scolding_interaction(self, sim_info, event, resolver):
        target = resolver.interaction.target
        if resolver.interaction.sim.sim_info is not sim_info:
            return
        if not self.owner.is_sim_in_situation(target):
            return
        self._scold_count += 1
        if self.inappropriate_behavior_threshold.compare(self._scold_count):
            dialog = self.scolding_notification(sim_info)
            dialog.show_dialog(secondary_icon_override=IconInfoData(obj_instance=sim_info))
        else:
            dialog = self.send_away_notification(sim_info)
            dialog.show_dialog(secondary_icon_override=IconInfoData(obj_instance=sim_info))
            sim = sim_info.get_sim_instance()
            context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.Critical)
            execute_result = sim.push_super_affordance(self.send_away_inappropriate_sim_interaction, target, context)
            if execute_result:
                execute_result.interaction.register_on_finishing_callback(self._sent_away_finished_callback)

    def _sent_away_finished_callback(self, interaction):
        if not interaction.is_finishing_naturally:
            return
        self.owner._change_state(self.owner.leave_npc_house_state())
示例#29
0
class VetClinicTuning:
    UNIFORM_EMPLOYEE_MALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit male employee uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    UNIFORM_EMPLOYEE_FEMALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit female employee uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    VET_CLINIC_VENUE = TunablePackSafeReference(
        description=
        '\n        This is a tunable reference to the type of this Venue.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.VENUE))
    DEFAULT_PROFIT_PER_TREATMENT_FOR_OFF_LOT_SIMULATION = TunableRange(
        description=
        '\n        This is used as the default profit for a treatment for off-lot simulation.\n        Once enough actual treatments have been performed, this value becomes \n        irrelevant and the MAX_COUNT_FOR_OFF_LOT_PROFIT_PER_TREATMENT tunable comes into use. \n        ',
        tunable_type=int,
        default=20,
        minimum=1)
    MAX_COUNT_FOR_OFF_LOT_PROFIT_PER_TREATMENT = TunableRange(
        description=
        '\n        The number of treatments to keep a running average of for the profit\n        per treatment calculations during off lot simulations.\n        ',
        tunable_type=int,
        default=10,
        minimum=2)
    VET_SKILL = Skill.TunablePackSafeReference(
        description=
        '\n        The vet skill for reference in code.  This can resolve to None\n        if the pack providing the skill is not installed, so beware.\n        '
    )
    VALUE_OF_SERVICE_AWARDS = TunableList(
        description=
        '\n        A threshold matrix that maps buffs to level of markup and vet skill.\n\n        Order is important.  The list is processed in reverse order.\n        The first threshold that passes returns the amount associated with it.\n        Because of this, optimal order is thresholds is ordered from lesser \n        to greater threshold values.\n        ',
        tunable=TunableTuple(
            description=
            '\n            A pair of markup threshold and skill threshold-to-buff list.\n            ',
            markup_threshold=TunableThreshold(
                description='The threshold at which this item will match.'),
            skill_to_buffs=TunableList(
                description=
                '\n                Mapping of skill threshold to the value of service that is applied.\n                \n                Order is important.  The list is processed in reverse order.\n                The first threshold that passes returns the amount associated with it.\n                Because of this, optimal order is thresholds is ordered from lesser \n                to greater threshold values.\n                ',
                tunable=TunableTuple(
                    description=
                    "\n                    A pair of skill threshold to the buff that will apply\n                    if this threshold is met when the patient is billed\n                    for a vet's services.\n                    ",
                    skill_range=SkillRangeTest.TunableFactory(
                        skill_range=SkillThreshold.TunableFactory(),
                        callback=set_vet_skill_on_threshold_test,
                        locked_args={
                            'subject': ParticipantType.Actor,
                            'skill': None,
                            'tooltip': None
                        }),
                    value_of_service_buff=TunableReference(
                        manager=services.get_instance_manager(Types.BUFF),
                        pack_safe=True)))),
        verify_tunable_callback=verify_value_of_service)
    DIFFICULTY_BONUS_PAYMENT = TunableList(
        description=
        '\n        When an NPC or player Sim treats an NPC Sim, they can get a difficulty\n        bonus depending on the difficulty of the sickness (if it is the correct\n        and ideal treatment for the sickness).\n        \n        Order is important.  The list is processed in reverse order.\n        The first threshold that passes returns the amount associated with it.\n        Because of this, optimal order is thresholds is ordered from lesser \n        to greater threshold values.\n        \n        If no thresholds pass, returned bonus amount is 0.\n        ',
        tunable=TunableTuple(
            description=
            '\n            A pair of payment amount and threshold that the payment applies to.\n            ',
            bonus_amount=TunableRange(tunable_type=int, default=100,
                                      minimum=0),
            threshold=TunableThreshold()),
        verify_tunable_callback=verify_difficulty_bonuses)
class ObjectRelationshipComponent(Component, HasTunableFactory, component_name=types.OBJECT_RELATIONSHIP_COMPONENT, persistence_key=protocols.PersistenceMaster.PersistableData.ObjectRelationshipComponent):
    __qualname__ = 'ObjectRelationshipComponent'
    FACTORY_TUNABLES = {'number_of_allowed_relationships': OptionalTunable(description='\n            Number of Sims who can have a relationship with this object at one\n            time.  If not specified, an infinite number of Sims can have a \n            relationship with the object.\n            ', tunable=TunableRange(tunable_type=int, default=1, minimum=1)), 'relationship_stat': Statistic.TunableReference(description="\n            The statistic which will be created for each of this object's\n            relationships.\n            "), 'relationship_track_visual': OptionalTunable(RelationshipTrack.TunableReference(description='\n                The relationship that this track will visually try and imitate in\n                regards to static track tack data.  If this is None then this\n                relationship will not be sent down to the client.\n                ')), 'relationship_based_state_change_tuning': OptionalTunable(TunableTuple(description='\n            A list of value ranges and associated states.  If the active Sim\n            has a relationship with this object  that falls within one of the\n            value ranges specified here, the object will change state to match\n            the specified state.\n            \n            These state changes exist on a per Sim basis, so this tuning will\n            effectively make the same object appear different depending on\n            which Sim is currently active.\n            ', state_changes=TunableList(tunable=TunableTuple(value_threshold=TunableThreshold(description="\n                        The range that the active Sim's relationship with this\n                        object must fall within in order for this state change to\n                        take place.\n                        "), state=TunableStateValueReference(description="\n                        The state this object will change to if it's relationship\n                        with the active Sim falls within the specified range.\n                        "))), default_state=TunableStateValueReference(description='\n                The state this object will change to if there is no other tuned\n                relationship based state change for the currently active Sim.\n                ')))}

    def __init__(self, owner, number_of_allowed_relationships, relationship_stat, relationship_track_visual, relationship_based_state_change_tuning):
        super().__init__(owner)
        self._number_of_allowed_relationships = number_of_allowed_relationships
        self._relationship_stat = relationship_stat
        self._relationship_track_visual = relationship_track_visual
        self._relationship_based_state_change_tuning = relationship_based_state_change_tuning
        self._state_changes = None
        self._default_state = None
        self._relationships = {}
        self._relationship_changed_callbacks = defaultdict(CallableList)

    def _on_active_sim_change(self, _, new_sim):
        if new_sim is None:
            return
        relationship = self._get_relationship(new_sim.id)
        self._update_state(relationship)

    def _update_state(self, relationship):
        if self._default_state is None:
            return
        if relationship is None:
            new_state = self._default_state
        elif self._state_changes is None:
            new_state = self._default_state
        else:
            for state_change in self._state_changes:
                while state_change.value_threshold.compare(relationship.get_value()):
                    new_state = state_change.state
                    break
            new_state = self._default_state
        self.owner.set_state(new_state.state, new_state)

    @property
    def _can_add_new_relationship(self):
        if self._number_of_allowed_relationships is not None and len(self._relationships) >= self._number_of_allowed_relationships:
            return False
        return True

    def on_add(self):
        if self._relationship_based_state_change_tuning is None:
            return
        self._state_changes = self._relationship_based_state_change_tuning.state_changes
        self._default_state = self._relationship_based_state_change_tuning.default_state
        services.current_zone().register_callback(zone_types.ZoneState.CLIENT_CONNECTED, self._register_active_sim_change)
        services.current_zone().register_callback(zone_types.ZoneState.HOUSEHOLDS_AND_SIM_INFOS_LOADED, self._publish_relationship_data)

    def on_remove(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.unregister_active_sim_changed(self._on_active_sim_change)

    def _register_active_sim_change(self):
        client = services.client_manager().get_first_client()
        if client is not None:
            client.register_active_sim_changed(self._on_active_sim_change)

    def _publish_relationship_data(self):
        if not self._relationship_track_visual:
            return
        for sim_id in self._relationships.keys():
            self._send_relationship_data(sim_id)

    def add_relationship_changed_callback_for_sim_id(self, sim_id, callback):
        self._relationship_changed_callbacks[sim_id].append(callback)

    def remove_relationship_changed_callback_for_sim_id(self, sim_id, callback):
        if sim_id in self._relationship_changed_callbacks and callback in self._relationship_changed_callbacks[sim_id]:
            self._relationship_changed_callbacks[sim_id].remove(callback)

    def _trigger_relationship_changed_callbacks_for_sim_id(self, sim_id):
        callbacks = self._relationship_changed_callbacks[sim_id]
        if callbacks is not None:
            callbacks()

    def add_relationship(self, sim_id):
        if sim_id in self._relationships:
            return False
        if not self._can_add_new_relationship:
            return False
        stat = self._relationship_stat(None)
        self._relationships[sim_id] = stat
        stat.on_add()
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        return True

    def remove_relationship(self, sim_id):
        if sim_id not in self._relationships:
            return
        del self._relationships[sim_id]
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)

    def modify_relationship(self, sim_id, value, add=True):
        if not add:
            return
        if not (sim_id not in self._relationships and self.add_relationship(sim_id)):
            return
        self._relationships[sim_id].add_value(value)
        self._send_relationship_data(sim_id)
        self._trigger_relationship_changed_callbacks_for_sim_id(sim_id)
        client = services.client_manager().get_first_client()
        if client is not None and client.active_sim is not None and client.active_sim.sim_id == sim_id:
            self._update_state(self._relationships[sim_id])

    def _get_relationship(self, sim_id):
        return self._relationships.get(sim_id)

    def has_relationship(self, sim_id):
        return sim_id in self._relationships

    def get_relationship_value(self, sim_id):
        relationship = self._get_relationship(sim_id)
        if relationship is not None:
            return relationship.get_value()
        return self._relationship_stat.initial_value

    def get_relationship_initial_value(self):
        return self._relationship_stat.initial_value

    def _send_relationship_data(self, sim_id):
        if self._relationship_track_visual is None:
            return
        relationship_to_send = self._get_relationship(sim_id)
        if not relationship_to_send:
            return
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return
        msg = commodity_protocol.RelationshipUpdate()
        msg.actor_sim_id = sim_id
        (msg.target_id.object_id, msg.target_id.manager_id) = self.owner.icon_info
        msg.target_instance_id = self.owner.id
        with ProtocolBufferRollback(msg.tracks) as relationship_track_update:
            relationship_value = relationship_to_send.get_value()
            relationship_track_update.track_score = relationship_value
            relationship_track_update.track_bit_id = self._relationship_track_visual.get_bit_at_relationship_value(relationship_value).guid64
            relationship_track_update.track_id = self._relationship_track_visual.guid64
            relationship_track_update.track_popup_priority = self._relationship_track_visual.display_popup_priority
        send_relationship_op(sim_info, msg)

    def save(self, persistence_master_message):
        if not self._relationships:
            return
        persistable_data = protocols.PersistenceMaster.PersistableData()
        persistable_data.type = protocols.PersistenceMaster.PersistableData.ObjectRelationshipComponent
        relationship_component_data = persistable_data.Extensions[protocols.PersistableObjectRelationshipComponent.persistable_data]
        for (key, value) in self._relationships.items():
            with ProtocolBufferRollback(relationship_component_data.relationships) as relationship_data:
                relationship_data.sim_id = key
                relationship_data.value = value.get_value()
        persistence_master_message.data.extend([persistable_data])

    def load(self, persistable_data):
        relationship_component_data = persistable_data.Extensions[protocols.PersistableObjectRelationshipComponent.persistable_data]
        for relationship in relationship_component_data.relationships:
            self.modify_relationship(relationship.sim_id, relationship.value)