예제 #1
0
class UniversitySpecificSpawnPointTags(HasTunableSingletonFactory,
                                       AutoFactoryInit):
    FACTORY_TUNABLES = {
        'spawn_point_tags':
        TunableMapping(
            description=
            '\n            University specific classroom tags.\n            ',
            key_type=TunableReference(
                manager=services.get_instance_manager(Types.UNIVERSITY)),
            value_type=TunableSet(tunable=TunableEnumWithFilter(
                tunable_type=Tag,
                default=Tag.INVALID,
                filter_prefixes=('Spawn', )),
                                  minlength=1))
    }

    def get_tags(self, sim_info, interaction):
        degree_tracker = sim_info.degree_tracker
        if degree_tracker is None:
            logger.error(
                'Trying to get University Specific spawn point from sim {} with no degree tracker',
                sim_info)
            return EMPTY_SET
        university = degree_tracker.get_university()
        if university not in self.spawn_point_tags:
            return EMPTY_SET
        return self.spawn_point_tags[university]
예제 #2
0
def _get_tunable_household_member_list(template_type, is_optional=False):
    if template_type == SimTemplateType.PREMADE_HOUSEHOLD:
        template_reference_type = PremadeSimTemplate
    else:
        template_reference_type = TunableSimTemplate
    tuple_elements = {
        'sim_template':
        template_reference_type.TunableReference(
            description=
            '            \n            A template to use for creating a household member. If this\n            references a resource that is not installed, the household member is\n            ignored and the family is going to be created without this\n            individual.\n            ',
            pack_safe=is_optional),
        'household_member_tag':
        TunableEnumWithFilter(
            description=
            '            \n            Tag to be used to create relationship between sim members. This does\n            NOT have to be unique for all household templates. If you want to\n            add more tags in the tag tuning just add with prefix of\n            household_member.r.\n            ',
            tunable_type=tag.Tag,
            default=tag.Tag.INVALID,
            filter_prefixes=HOUSEHOLD_FILTER_PREFIX)
    }
    if is_optional:
        tuple_elements['chance'] = TunablePercent(
            description=
            '\n            The chance that this household member is created when the household\n            is created. This is useful for "optional" Sims. For example, you\n            might want to tune a third of typical nuclear families to own a dog,\n            should the resource be available.\n            ',
            default=100)
    else:
        tuple_elements['locked_args'] = {'chance': 1}
    return TunableList(
        description=
        '\n        A list of sim templates that will make up the sims in this household.\n        ',
        tunable=TunableTuple(**tuple_elements))
예제 #3
0
class DestroySituationsByTagsMixin:
    FACTORY_TUNABLES = {
        'situation_tags':
        TunableSet(
            description=
            '\n            A situation must match at least one of the tuned tags in order to\n            be destroyed.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=Tag,
                                          filter_prefixes=['situation'],
                                          default=Tag.INVALID,
                                          pack_safe=True)),
        'required_participant':
        TunableEnumFlags(
            description=
            '\n            If tuned, only situations with this participant will be destroyed.\n            ',
            enum_type=ParticipantType,
            default=ParticipantType.Invalid)
    }

    def _destroy_situations_by_tags(self, resolver):
        situation_manager = services.get_zone_situation_manager()
        situations = situation_manager.get_situations_by_tags(
            self.situation_tags)
        if situations:
            participant = None
            if self.required_participant is not None and self.required_participant != ParticipantType.Invalid:
                participant = resolver.get_participant(
                    self.required_participant)
                if participant is None or not participant.is_sim:
                    return False
            for situation in situations:
                if participant and not situation.is_sim_info_in_situation(
                        participant.sim_info):
                    continue
                situation_manager.destroy_situation_by_id(situation.id)
        return True
예제 #4
0
class FireTuning:
    FLAMMABLE_TAG = TunableEnumWithFilter(
        description=
        '\n        Define a tag that is automatically added to all objects that are\n        flammable.\n        ',
        tunable_type=Tag,
        default=Tag.INVALID,
        invalid_enums=(Tag.INVALID, ),
        filter_prefixes=('Fire', ))
예제 #5
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
예제 #6
0
class OutfitGenerator(OutfitGeneratorRandomizationMixin,
                      HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'tags':
        TunableSet(
            description=
            '\n            The set of tags used to generate the outfit. Parts must match the\n            specified tag in order to be valid for the generated outfit.\n            ',
            tunable=TunableEnumWithFilter(
                tunable_type=Tag,
                filter_prefixes=('Uniform', 'OutfitCategory', 'Style',
                                 'Situation'),
                default=Tag.INVALID,
                invalid_enums=(Tag.INVALID, ),
                pack_safe=True))
    }

    def __call__(self, *args, **kwargs):
        self._generate_outfit(*args, tag_list=self.tags, **kwargs)
class UniversityHousingTuning:
    UNIVERSITY_HOUSING_KICK_OUT_SITUATION = TunableReference(
        description=
        '\n        The situation to kick a sim out of university housing.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=('UniversityHousingKickOutSituation', ),
        pack_safe=True)
    UNIVERSITY_HOUSING_ROOMMATE_FILTER_TERM_TEMPLATES = TunableMapping(
        description=
        "\n        Template filter terms for each university housing roommate requirement criteria.\n        We will be modifying these terms in code based on what the player sets\n        in the venue's configuration UI. For example, the template gender filter\n        term is set to male, but if the player sets their university housing\n        venue as female only, we will modify this filter term before retrieving\n        roommates.  These values are set in tuning so the majority of the filter\n        terms' values are initialized to their defaults, instead of having\n        to do so in code.\n        ",
        key_type=TunableEnumEntry(
            tunable_type=UniversityHousingRoommateRequirementCriteria,
            default=UniversityHousingRoommateRequirementCriteria.NONE,
            invalid_enums=UniversityHousingRoommateRequirementCriteria.NONE,
            pack_safe=True),
        key_name='Requirement Criteria',
        value_type=FilterTermVariant(),
        value_name='Filter Term Template')
    UNIVERSITY_HOUSING_VENUE_TUNING = TunablePackSafeReference(
        description='\n        The university housing venue.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.VENUE))
    UNIVERSITY_HOUSING_PREGNANCY_TEST = TunableTestSet(
        description=
        '\n        Test to determine if a sim is at the appropriate stage in a pregnancy\n        in order to be kicked out of university housing.\n        '
    )
    UNIVERSITY_HOUSING_VALIDATION_CADENCE = TunableTimeSpan(
        description=
        '\n        When a university housing venue is loaded, the timespan between updates\n        where we validate household sims to decide if they need to be kicked out.\n        '
    )
    UNIVERSITY_HOUSING_KICKOUT_SITUATION_BLOCKER_TAG = TunableEnumWithFilter(
        description=
        "\n        If a situation with this tag is running, we won't start any kickout situations.  We use this tag to prevent\n        edge cases such as multiple kickouts running at the same time, or kicking out sims who have died.\n        ",
        tunable_type=Tag,
        filter_prefixes=['situation'],
        default=Tag.INVALID,
        pack_safe=True)

    @staticmethod
    def get_university_housing_zone_ids():
        university_venue_tuning = UniversityHousingTuning.UNIVERSITY_HOUSING_VENUE_TUNING
        if university_venue_tuning is None:
            return ()
        return tuple(services.venue_service().get_zones_for_venue_type_gen(
            university_venue_tuning))
예제 #8
0
class Spell(_SpellDisplayMixin,
            SuperAffordanceProviderMixin,
            TargetSuperAffordanceProviderMixin,
            metaclass=HashedTunedInstanceMetaclass,
            manager=services.get_instance_manager(Types.SPELL)):
    INSTANCE_TUNABLES = {
        'locked_description':
        OptionalTunable(
            description=
            '\n            Description used in the spellbook if spell is not yet unlocked.\n            If unset, uses display data description.\n            ',
            tunable=TunableLocalizedString(),
            tuning_group=GroupNames.UI),
        'ingredients':
        ItemCost.TunableFactory(
            description=
            '\n            Ingredients needed to cast the spell.  Interactions which specify this spell as the item cost will consume \n            the ingredients specified here.\n            '
        ),
        'tags':
        TunableSet(
            description='\n            Tags for the spell.\n            ',
            tunable=TunableEnumWithFilter(
                tunable_type=Tag,
                filter_prefixes=['spell'],
                default=Tag.INVALID,
                invalid_enums=(Tag.INVALID, ),
                pack_safe=True,
                binary_type=EnumBinaryExportType.EnumUint32),
            export_modes=ExportModes.All,
            tuning_group=GroupNames.TAG)
    }

    @classmethod
    def get_display_name(cls, *_):
        return cls.display_name

    @classproperty
    def unlock_as_new(cls):
        return True

    @classproperty
    def tuning_tags(cls):
        return cls.tags
예제 #9
0
class SynchInSituationMixerInteraction(SynchMixerInteraction):
    INSTANCE_TUNABLES = {
        'situation_tags':
        TunableSet(
            description=
            '\n            Tags for arbitrary groupings of situation types.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=Tag,
                                          filter_prefixes=['situation'],
                                          default=Tag.INVALID,
                                          pack_safe=True))
    }

    def get_sims(self):
        sim_list = []
        situation_manager = services.get_zone_situation_manager()
        situation_list = situation_manager.get_situations_by_tags(
            self.situation_tags)
        for situation in situation_list:
            if situation.is_sim_in_situation(self.sim):
                sim_list.extend(situation.all_sims_in_situation_gen())
        return sim_list
class AttractorPointInteraction(SuperInteraction):
    INSTANCE_TUNABLES = {
        '_attractor_point_identifier':
        TunableEnumWithFilter(
            description=
            '\n            The identifier that will be used to select which attractor points\n            we will use.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            filter_prefixes=('AtPo', ),
            tuning_group=GroupNames.PICKERTUNING),
        '_attractor_point_interaction':
        SuperInteraction.TunableReference(
            description=
            '\n            The affordance that we will push on the Sim once the attractor\n            point selection has been made.\n            ',
            tuning_group=GroupNames.PICKERTUNING)
    }

    @classmethod
    def _test(cls, target, context, **interaction_parameters):
        attractor_points = list(
            services.object_manager().get_objects_with_tag_gen(
                cls._attractor_point_identifier))
        if not attractor_points:
            return results.TestResult(
                False, 'No attractor points with tag {} found.',
                cls._attractor_point_identifier)
        return super()._test(target, context, **interaction_parameters)

    def _run_interaction_gen(self, timeline):
        attractor_points = list(
            services.object_manager().get_objects_with_tag_gen(
                self._attractor_point_identifier))
        chosen_point = random.choice(attractor_points)
        context = self.context.clone_for_continuation(self)
        self.sim.push_super_affordance(self._attractor_point_interaction,
                                       chosen_point, context)
        return True
        yield
class MultipleSimInteractionOfInterest(AutoFactoryInit):
    __qualname__ = 'MultipleSimInteractionOfInterest'
    FACTORY_TUNABLES = {
        'affordance':
        TunableReference(
            description=
            '\n                The affordance in question that is being run by all the sims.\n                ',
            manager=services.affordance_manager(),
            class_restrictions='SuperInteraction'),
        'tags':
        TunableSet(
            description=
            '\n                A set of tags that match the affordance being run by all the sims. \n                ',
            tunable=TunableEnumWithFilter(
                tunable_type=tag.Tag,
                default=tag.Tag.INVALID,
                filter_prefixes=tag.INTERACTION_PREFIX)),
        'sim_count':
        Tunable(
            description=
            '\n                The number of sims simultaneously running the appropriate interactions.\n                ',
            tunable_type=int,
            default=2)
    }

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

    def __call__(self, interaction=None):
        if interaction.affordance is self.affordance:
            return TestResult.TRUE
        if self.tags & interaction.get_category_tags():
            return TestResult.TRUE
        return TestResult(
            False,
            'Failed affordance check: {} is not {} and does not have any matching tags in {}.',
            interaction.affordance, self.affordance, self.tags)
예제 #12
0
class UniversityCourseData(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(Types.UNIVERSITY_COURSE_DATA)):
    INSTANCE_TUNABLES = {'spawn_point_tag': TunableMapping(description='\n            University specific spawn point tags.\n            Used by course related interactions to determine which spawn\n            point to use for the constraint. (i.e. the one in front of the\n            appropriate building)\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableSet(tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('Spawn',)), minlength=1)), 'classroom_tag': TunableMapping(description='\n            University specific classroom tags.\n            Used by university interactions on shells to determine which building\n            shell should have the interaction(s) available.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableSet(tunable=TunableEnumEntry(tunable_type=Tag, default=Tag.INVALID), minlength=1)), 'university_course_mapping': TunableMapping(description='\n            University specific course name and description.\n            Each university can have its own course name and description\n            defined.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableTuple(course_name=TunableLocalizedStringFactory(description='\n                    The name of this course.\n                    '), course_description=TunableLocalizedString(description='\n                    A description for this course.\n                    ', allow_none=True), export_class_name='UniversityCourseDisplayData'), tuple_name='UniversityCourseDataMapping', export_modes=ExportModes.All), 'course_skill_data': TunableTuple(description='\n            The related skill data for this specific course.  Whenever a Sim \n            does something that increases their course grade performance (like\n            attending lecture or studying), this skill will also increase by\n            the tunable amount.  Likewise, whenever this related skill \n            increases, the course grade will also increase.\n            ', related_skill=OptionalTunable(description='\n                The related skill associated with this course.\n                ', tunable=TunablePackSafeReference(manager=services.get_instance_manager(Types.STATISTIC), class_restrictions=('Skill',)))), 'icon': TunableIcon(description='\n            Icon for this university course.\n            ', export_modes=ExportModes.All, allow_none=True), 'cost': TunableRange(description='\n            The cost of this course.\n            ', tunable_type=int, default=200, minimum=0, export_modes=ExportModes.All), 'course_tags': TunableTags(description='\n            The tag for this course.  Used for objects that may be shared \n            between courses.\n            ', filter_prefixes=['course']), 'final_requirement_type': TunableEnumEntry(description='\n            The final requirement for this course.  This requirement must be \n            completed before the course can be considered complete.\n            ', tunable_type=FinalCourseRequirement, default=FinalCourseRequirement.NONE), 'final_requirement_aspiration': TunableReference(description='\n            An aspiration to use for tracking the final course requirement. \n            ', manager=services.get_instance_manager(sims4.resources.Types.ASPIRATION), class_restrictions='AspirationAssignment', allow_none=True), 'professor_assignment_trait': TunableMapping(description='\n            A mapping of University -> professor assignment trait.\n            \n            This is needed because each of the universities shipped with EP08\n            use the exact same classes but we want different teachers for each\n            university.\n            ', key_type=TunableReference(description='\n                A reference to the University that the professor will belong to.\n                ', manager=services.get_instance_manager(sims4.resources.Types.UNIVERSITY)), value_type=TunableReference(description='\n                The trait used to identify the professor for this course.\n                ', manager=services.get_instance_manager(sims4.resources.Types.TRAIT)))}

    @classproperty
    def is_elective(cls):
        return any(cls is e.elective for e in University.COURSE_ELECTIVES.electives)
예제 #13
0
class DynamicSpawnPointElement(SubclassableGeneratorElement, HasTunableFactory,
                               AutoFactoryInit):
    __qualname__ = 'DynamicSpawnPointElement'
    FACTORY_TUNABLES = {
        'description':
        '\n            This Element will create a Dynamic Spawn Point which is registered\n            to a particular participant within the interaction. It will be\n            added to the zone and available for use by any Sims who want to\n            spawn.\n            ',
        'tags':
        TunableSet(
            description=
            "\n            A set of tags to add to the dynamic spawn point when it's created.\n            This is how we can use this spawn point to spawn particular Sims\n            without interfering with walkbys and other standard Sims that are\n            spawned.\n            ",
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=tag.SPAWN_PREFIX)),
        'participant':
        TunableEnumEntry(
            description=
            '\n            The Participant of the interaction that we want the spawn point to be near.\n            ',
            tunable_type=ParticipantType,
            default=ParticipantType.Actor),
        'attach_to_active_lot':
        Tunable(
            description=
            '\n            If checked, the spawn point will be attached to the active lot.\n            This helps Sims who are looking to visit the current lot find a\n            spawn point nearby.\n            ',
            tunable_type=bool,
            default=False),
        'distance_to_participant':
        Tunable(
            description=
            '\n            The Distance from the participant that Sims should spawn.\n            ',
            tunable_type=float,
            default=7.0)
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(*args, **kwargs)
        self.interaction = interaction
        self.sequence = sequence
        self.spawn_point = None

    def _run_gen(self, timeline):
        def begin(_):
            zone = services.current_zone()
            lot_id = 0 if not self.attach_to_active_lot else zone.lot.lot_id
            self.spawn_point = DynamicInteractionSpawnPoint(
                self.interaction,
                self.participant,
                self.distance_to_participant,
                self.tags,
                lot_id=lot_id,
                zone_id=zone.id)
            services.current_zone().add_dynamic_spawn_point(self.spawn_point)

        def end(_):
            services.current_zone().remove_dynamic_spawn_point(
                self.spawn_point)
            self.spawn_point = None

        result = yield element_utils.run_child(
            timeline,
            build_critical_section_with_finally(begin, self.sequence, end))
        return result
예제 #14
0
class ChefsChoice:
    FOOD_COURSES = TunableList(
        description=
        '\n        A List of all the courses to search through in order to find what an \n        NPC will order.\n        ',
        tunable=TunableEnumWithFilter(
            description=
            '\n            A food course that an NPC can order.\n            ',
            tunable_type=Tag,
            filter_prefixes=['recipe_course'],
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True))
    DRINK_COURSE = TunableEnumWithFilter(
        description=
        '\n        The drink course so Sims can order drinks with their meals.\n        ',
        tunable_type=Tag,
        filter_prefixes=['recipe_course'],
        default=Tag.INVALID,
        invalid_enums=(Tag.INVALID, ),
        pack_safe=True)
    DESSERT_COURSE = TunableEnumWithFilter(
        description=
        '\n        The dessert course so Sims can order dessert with their meals.\n        ',
        tunable_type=Tag,
        filter_prefixes=['recipe_course'],
        default=Tag.INVALID,
        invalid_enums=(Tag.INVALID, ),
        pack_safe=True)
    NPC_ORDER_MAP = TunableMapping(
        description=
        '\n        A mapping of tags to weighted tests. If an item on the menu has the\n        designated tag, it will start with the tuned base weight and then each\n        passing test will add the tested-weight to the total weight for that\n        food object. Once all food objects have been weighed for a given\n        category (apps, entrees, etc.), a weighted random determines the\n        winner.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            If the food item has this tag, we will apply the corresponding base\n            weight to it and the sum of the weights of any passing tests run on\n            this object.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            The base weight and weighted tests to run.\n            ',
            base_weight=TunableRange(
                description=
                '\n                The base weight of this food object. Even if no tests pass,\n                this weight will be applied for use with the weighted random\n                selection.\n                ',
                tunable_type=float,
                default=1.0,
                minimum=0),
            weighted_tests=TunableList(
                description=
                '\n                A list of tests and weights. For each passed test, the\n                corresponding weight is added to the base weight of the food\n                object.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Tests and weights. If the test passes, the weight is added\n                    to the base weight of the food object.\n                    ',
                    tests=TunableTestSet(),
                    weight=Tunable(
                        description=
                        '\n                        The weight to add to the base weight of the food object\n                        if the corresponding tests pass. A negative value is\n                        valid.\n                        ',
                        tunable_type=float,
                        default=1.0)))))
    WATER_ORDER_FOR_BACKUP = TunablePackSafeReference(
        description=
        '\n        A reference to the water order that should be available when nothing\n        else is available.\n        ',
        manager=services.recipe_manager(),
        class_restrictions=('Recipe', ))

    @classmethod
    def get_choice_for_npc_sim(cls, sim, course):
        zone_director = get_restaurant_zone_director()
        menu_items = zone_director.get_menu_for_course(course)
        possible_items = cls.get_possible_orders(sim, menu_items)
        if not possible_items:
            return
        choice = random.weighted_random_item(list(possible_items.items()),
                                             flipped=True)
        return choice

    @classmethod
    def get_order_for_npc_sim(cls, sim):
        zone_director = get_restaurant_zone_director()
        if zone_director is None:
            logger.error(
                'Trying to get an order for an NPC sim but there is no restaurant zone director.'
            )
            return
        menu_items = []
        for course in cls.FOOD_COURSES:
            menu_items.extend(zone_director.get_menu_for_course(course))
        possible_orders = list(
            cls.get_possible_orders(sim, menu_items).items())
        food_choice = random.weighted_random_item(possible_orders,
                                                  flipped=True)
        business_manager = services.business_service(
        ).get_business_manager_for_zone()
        if business_manager is not None:
            bucks_tracker = services.active_household().bucks_tracker
            if bucks_tracker.is_perk_unlocked(
                    RestaurantTuning.CUSTOMERS_ORDER_EXPENSIVE_FOOD_PERK_DATA.
                    perk):
                food_choice_2 = random.weighted_random_item(possible_orders,
                                                            flipped=True)
                if food_choice_2 is not food_choice:
                    choice_1_price = business_manager.get_value_with_markup(
                        food_choice.restaurant_base_price)
                    choice_2_price = business_manager.get_value_with_markup(
                        food_choice_2.restaurant_base_price)
                    if choice_2_price > choice_1_price:
                        food_choice = food_choice_2
        drink_choice = cls.get_choice_for_npc_sim(sim, cls.DRINK_COURSE)
        if food_choice is None and drink_choice is None:
            return (None, cls.WATER_ORDER_FOR_BACKUP)
        return (food_choice, drink_choice)

    @classmethod
    def get_order_for_npc_sim_with_menu(cls, sim, menu_preset):
        chef_menu = RestaurantTuning.MENU_PRESETS[menu_preset]
        menu_items = []
        for course in cls.FOOD_COURSES:
            menu_items.extend(chef_menu.recipe_map.get(course, {}))
        possible_orders = cls.get_possible_orders(sim, menu_items)
        food_order = random.weighted_random_item(list(possible_orders.items()),
                                                 flipped=True)
        return food_order

    @classmethod
    def get_possible_orders(cls, sim, menu_items):
        resolver = SingleSimResolver(sim)
        possible_orders = {}
        for (order_data_tag, order_data) in cls.NPC_ORDER_MAP.items():
            for recipe in menu_items:
                if order_data_tag in recipe.recipe_tags:
                    if recipe not in possible_orders:
                        possible_orders[recipe] = 0
                    possible_orders[recipe] += order_data.base_weight
                    for weighted_test in order_data.weighted_tests:
                        if weighted_test.tests.run_tests(resolver):
                            possible_orders[recipe] += weighted_test.weight
        return possible_orders
class HouseholdTemplate(HasTunableReference, metaclass=sims4.tuning.instances.TunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SIM_TEMPLATE)):
    __qualname__ = 'HouseholdTemplate'
    INSTANCE_TUNABLES = {'_household_members': TunableList(description='\n            A list of sim templates that will make up the sims in this household.\n            ', tunable=TunableTuple(sim_template=filters.sim_template.TunableSimTemplate.TunableReference(description='\n                    A template to use for creating household member\n                    '), household_member_tag=TunableEnumWithFilter(description='\n                        Tag to be used to create relationship between sim\n                        members.  This does NOT have to be unique for all\n                        household templates. If you want to add more tags\n                        in the tag tuning just add with prefix of\n                        household_member.\n                        ', tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=HOUSEHOLD_FILTER_PREFIX))), '_household_funds': TunableRange(description='\n            Starting funds for this household.\n            ', tunable_type=int, default=20000, minimum=0, maximum=99999999), '_household_relationship': TunableList(description='\n            Matrix of relationship that should be applied to household members.\n            ', tunable=TunableTuple(x=TunableEnumWithFilter(description='\n                    Tag of the household member to apply relationship to.\n                    ', tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=HOUSEHOLD_FILTER_PREFIX), y=TunableEnumWithFilter(description='\n                    Tag of the household member to be the target of relationship.\n                    ', tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=HOUSEHOLD_FILTER_PREFIX), is_spouse=Tunable(description='\n                    Check if x and y are spouses.\n                    ', tunable_type=bool, default=False), family_relationship=TunableEnumEntry(description='\n                        This is the family relationship between x and y.\n                        Example: if set to Father, x is the the father of y.\n                        ', tunable_type=sims.genealogy_tracker.FamilyRelationshipIndex, default=None), relationship_bits=TunableList(description='\n                    Relationship bits that should be applied to x with\n                    the target y. Any bits with a relationship track will add\n                    relationship track at value that will add the bit to both\n                    sims.  Any bits without Triggered track will only be\n                    applied only to x unless it is a Siginificant other Bit.\n                    \n                    Example: If friendship-friend bit is supplied which has a\n                    triggered track of LTR_Friendship_Main, then\n                    LTR_Frienship_main will be added to both sims with a random\n                    value of the min/max value of the bit data tuning that will\n                    supply bit.\n                    ', tunable=relationships.relationship_bit.RelationshipBit.TunableReference())))}

    @classmethod
    def _verify_tuning_callback(cls):
        tag_to_household_member_index = {}
        for (index, household_member_data) in enumerate(cls._household_members):
            while household_member_data.household_member_tag != tag.Tag.INVALID:
                household_member_tag = household_member_data.household_member_tag
                if household_member_tag in tag_to_household_member_index:
                    logger.error('Multiple household member have the same tag {}.  Orginally found at index:{}, but also set for index:{}', household_member_tag, tag_to_household_member_index[household_member_tag], index)
                else:
                    tag_to_household_member_index[household_member_tag] = index
        if cls._household_relationship and not tag_to_household_member_index:
            logger.error('Houshold relationship has been added but there are no tag info for household members.  Please update tuning and add tags to household members: {}.', cls.__name__)
            return
        family_relationship_mapping = {}
        spouse_pairs = []
        for (index, member_relationship_data) in enumerate(cls._household_relationship):
            x_member = member_relationship_data.x
            if x_member == tag.Tag.INVALID:
                logger.error('No tag set for x in household relationship at index {}. Please update tuning and set a tag', index)
            y_member = member_relationship_data.y
            if y_member == tag.Tag.INVALID:
                logger.error('No tag set for y in household relationship at index {}. Please update tuning and set a tag', index)
            if x_member not in tag_to_household_member_index:
                logger.error('The tag set for x :{} does not exist in household members. Please update tuning and update tag or set a household member with tag', x_member)
            if y_member not in tag_to_household_member_index:
                logger.error('The tag set for y :{} does not exist in household members. Please update tuning and update tag or set a household member with tag', y_member)
            if member_relationship_data.is_spouse:
                member_index = tag_to_household_member_index[x_member]
                household_member_data = cls._household_members[member_index]
                if household_member_data.sim_template._sim_creation_info.age_variant.min_age <= sim_info_types.Age.TEEN:
                    logger.error('Trying set spouse with sims of the inappropriate age.Check sim_template at index {} if set correctly.', member_index)
                member_index = tag_to_household_member_index[y_member]
                household_member_data = cls._household_members[member_index]
                if household_member_data.sim_template._sim_creation_info.age_variant.min_age <= sim_info_types.Age.TEEN:
                    logger.error('Trying set spouse with sims of the inappropriate age.Check sim_template at index {} if set correctly.', member_index)
                spouse_pairs.append((x_member, y_member, index))
                spouse_pairs.append((y_member, x_member, index))
            family_set_at_index = family_relationship_mapping.get((x_member, y_member))
            if family_set_at_index is not None:
                logger.error('There is already a family relationship between x_member and y_member.Family set at index:{} but also set at index: {}', family_set_at_index, index)
            while member_relationship_data.family_relationship is not None:
                family_relationship_mapping[(x_member, y_member)] = index
                family_relationship_mapping[(y_member, x_member)] = index
        for (x_member, y_member, household_relationship_index) in spouse_pairs:
            family_set_at_index = family_relationship_mapping.get((x_member, y_member))
            while family_set_at_index is not None:
                logger.error('Spouse is set for {} and {}, but also have family relationship. Update tuning: either uncheck spouse at index: {} or remove family relationship in household relationshipat index {}', x_member, y_member, household_relationship_index, family_set_at_index)

    @sims4.utils.classproperty
    def template_type(cls):
        return filters.sim_template.SimTemplateType.HOUSEHOLD

    @classmethod
    def get_household_members(cls):
        return cls._household_members

    @sims4.utils.classproperty
    def has_teen_or_below(cls):
        return any(household_member_data.sim_template._sim_creation_info.age_variant.min_age <= sim_info_types.Age.TEEN for household_member_data in cls._household_members)

    @sims4.utils.classproperty
    def num_members(cls):
        return len(cls._household_members)

    @sims4.utils.classproperty
    def has_spouse(cls):
        for household_relationship in cls._household_relationship:
            while household_relationship.is_spouse or Relationship.MARRIAGE_RELATIONSHIP_BIT in set(household_relationship.relationship_bits):
                return True
        return False

    @classmethod
    def create_household(cls, zone_to_fill_id, account, creation_source:str='household_template'):
        (household, _) = cls._create_household(zone_to_fill_id, account, [household_member.sim_template.sim_creator for household_member in cls._household_members], creation_source=creation_source)
        return household

    @classmethod
    def get_sim_infos_from_household(cls, zone_id, insertion_indexes_to_sim_creators, creation_source:str='household_template'):
        sim_creators = []
        for (index, household_member) in enumerate(cls._household_members):
            if index in insertion_indexes_to_sim_creators:
                sim_creators.append(insertion_indexes_to_sim_creators[index])
            else:
                sim_creators.append(household_member.sim_template.sim_creator)
        (household, sim_infos) = cls._create_household(zone_id, None, sim_creators, indexes_sim_info_to_return=insertion_indexes_to_sim_creators.keys(), creation_source=creation_source)
        return (household, sim_infos)

    @classmethod
    def _create_household(cls, home_zone_id, account, sim_creators, indexes_sim_info_to_return=None, creation_source:str='household_template'):
        if home_zone_id is None:
            home_zone_id = 0
        (created_sim_infos, household) = sims.sim_spawner.SimSpawner.create_sim_infos(sim_creators, zone_id=home_zone_id, account=account, starting_funds=cls._household_funds, creation_source=creation_source)
        household.home_zone_id = home_zone_id
        tag_to_sim_info = {}
        sim_infos_to_return = None if indexes_sim_info_to_return is None else []
        for (index, created_sim_info) in enumerate(created_sim_infos):
            household_member_data = cls._household_members[index]
            household_member_data.sim_template.add_template_data_to_sim(created_sim_info)
            created_sim_info.is_npc = True
            if household_member_data.household_member_tag != tag.Tag.INVALID:
                tag_to_sim_info[household_member_data.household_member_tag] = created_sim_info
            while indexes_sim_info_to_return is not None and index in indexes_sim_info_to_return:
                sim_infos_to_return.append(created_sim_info)
        if not tag_to_sim_info:
            return (household, sim_infos_to_return)
        for member_relationship_data in cls._household_relationship:
            source_sim_info = tag_to_sim_info.get(member_relationship_data.x)
            target_sim_info = tag_to_sim_info.get(member_relationship_data.y)
            if member_relationship_data.is_spouse:
                source_sim_info.update_spouse_sim_id(target_sim_info.id)
                target_sim_info.update_spouse_sim_id(source_sim_info.id)
            while member_relationship_data.family_relationship is not None:
                target_sim_info.set_and_propagate_family_relation(member_relationship_data.family_relationship, source_sim_info)
        household.set_default_relationships()
        for member_relationship_data in cls._household_relationship:
            source_sim_info = tag_to_sim_info.get(member_relationship_data.x)
            target_sim_info = tag_to_sim_info.get(member_relationship_data.y)
            for bit_to_add in member_relationship_data.relationship_bits:
                bit_triggered_track = bit_to_add.triggered_track
                if bit_triggered_track is not None:
                    bit_track_node = bit_to_add.triggered_track.get_bit_track_node_for_bit(bit_to_add)
                else:
                    bit_track_node = None
                if bit_track_node is not None:
                    rand_score = random.randint(bit_track_node.min_rel, bit_track_node.max_rel)
                    source_sim_info.relationship_tracker.add_relationship_score(target_sim_info.id, rand_score, bit_triggered_track)
                    target_sim_info.relationship_tracker.add_relationship_score(source_sim_info.id, rand_score, bit_triggered_track)
                else:
                    source_sim_info.relationship_tracker.add_relationship_bit(target_sim_info.id, bit_to_add, force_add=True)
                    while bit_to_add is Relationship.MARRIAGE_RELATIONSHIP_BIT:
                        target_sim_info.relationship_tracker.add_relationship_bit(source_sim_info.id, bit_to_add, force_add=True)
        return (household, sim_infos_to_return)
class OpenStreetsAutonomySituation(situations.situation_complex.SituationComplexCommon):
    __qualname__ = 'OpenStreetsAutonomySituation'
    INSTANCE_TUNABLES = {'role': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='\n                  The situation job for the sim.\n                  '), do_stuff_role_state=RoleState.TunableReference(description='\n                  The role state for the sim doing stuff.  This is the initial state.\n                  '), leave_role_state=RoleState.TunableReference(description='\n                  The role state for the sim leaving.\n                  ')), 'do_stuff_timeout': sims4.tuning.tunable.TunableSimMinute(description='\n            The amount of time the sim does stuff before leaving.\n            ', default=180), 'can_start_walkby_limiting_tags': TunableSet(description="\n                Don't start a situation of this type if another situation is\n                already running that has any of these tags in its tags field.\n                \n                For instance, if you only want one Streaker at a time you would\n                create a new tag SITUATION_STREAKER. Then set that in both this\n                field and in the tags field of situation_streaker.\n                ", tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=['situation']))}
    REMOVE_INSTANCE_TUNABLES = situations.situation.Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @staticmethod
    def _states():
        return [(1, _DoStuffState), (2, _LeaveState)]

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.role.situation_job, cls.role.do_stuff_role_state)]

    @classmethod
    def default_job(cls):
        return cls.role.situation_job

    def start_situation(self):
        super().start_situation()
        self._change_state(_DoStuffState())

    @classmethod
    def can_start_walkby(cls, lot_id):
        return not services.get_zone_situation_manager().is_situation_with_tags_running(cls.can_start_walkby_limiting_tags)

    @property
    def _should_cancel_leave_interaction_on_premature_removal(self):
        return True

    @classproperty
    def situation_serialization_option(cls):
        return situations.situation_types.SituationSerializationOption.OPEN_STREETS
class AttractorManagerMixin:
    ATTRACTOR_OBJECT_TAGS = TunableSet(description='\n        One or more tags that indicate an object is a type of attractor point.\n        We use attractor points to push Sims near things and reference specific\n        geography in the world.\n        ', tunable=TunableEnumWithFilter(description='\n            A specific tag.\n            ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID,), filter_prefixes=('AtPo',)))
    SPAWN_POINT_ATTRACTORS = TunableMapping(description='\n        Mapping from spawn point tags to attractor objects so we can create\n        attractor points at spawn points.\n        ', key_type=TunableEnumEntry(description='\n            The tag on the spawn point.\n            ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID,)), key_name='spawn point tag', value_type=TunableReference(description='\n            The object we want to create on the Spawn Point.\n            ', manager=services.definition_manager(), pack_safe=True), value_name='attractor point definition')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._dynamic_attractor_ids = WeakSet()

    def create_dynamic_attractor_object(self, definition_id, location, tags_to_add=None):
        tags_to_add = set() if tags_to_add is None else tags_to_add

        def setup_obj(obj):
            obj.append_tags(tags_to_add)
            obj.location = location
            obj.persistence_group = PersistenceGroups.NONE

        created_obj = objects.system.create_object(definition_id, init=setup_obj)
        self._dynamic_attractor_ids.add(created_obj)
        if not self.ATTRACTOR_OBJECT_TAGS.intersection(created_obj.get_tags()):
            logger.warn('Attractor object does not have any tags in the ATTRACTOR OBJECT TAGS list. We need to be able to locate attractor objects and keep track of them.')
        return created_obj

    def destroy_dynamic_attractor_object(self, object_id):
        obj_to_destroy = self.get(object_id)
        if obj_to_destroy is None:
            logger.error('Object {} is not a dynamic attractor point.', object_id)
            return
        self._dynamic_attractor_ids.discard(obj_to_destroy)
        obj_to_destroy.destroy(obj_to_destroy, cause='Destroying Dynamic Attractor Point')

    def get_attractor_objects(self):
        return self.get_objects_matching_tags(AttractorManagerMixin.ATTRACTOR_OBJECT_TAGS)

    def create_spawn_point_attractor(self, spawn_point):
        obj_ids = set()
        for (spawn_point_tag, attractor_definition) in self.SPAWN_POINT_ATTRACTORS.items():
            tags = spawn_point.get_tags()
            if spawn_point_tag in tags:
                location = sims4.math.Location(transform=spawn_point.get_approximate_transform(), routing_surface=spawn_point.routing_surface)
                obj = self.create_dynamic_attractor_object(attractor_definition, location, tags_to_add={spawn_point_tag})
                obj_ids.add(obj.id)
        return frozenset(obj_ids)
예제 #18
0
class RestaurantTagTuning:
    DINING_SPOT_OBJECT_TEST_TAG = TunableEnumEntry(description='\n        The enum tag to identify required object test in venue tuning for the dining spot.\n        ', tunable_type=VenueObjectTestTag, default=VenueObjectTestTag.INVALID, invalid_enums=(VenueObjectTestTag.INVALID,))
    RECIPE_FOOD_TAG = TunableEnumWithFilter(description='\n        The recipe tag to determine if this recipe is food recipe.\n        ', tunable_type=Tag, filter_prefixes=['recipe'], default=Tag.INVALID, invalid_enums=(Tag.INVALID,), pack_safe=True)
    RECIPE_DRINK_TAG = TunableEnumWithFilter(description='\n        The recipe tag to determine if this recipe is drink recipe.\n        ', tunable_type=Tag, filter_prefixes=['recipe'], default=Tag.INVALID, invalid_enums=(Tag.INVALID,), pack_safe=True)
    DINER_SITUATION_TAG = TunableEnumWithFilter(description='\n        The situation tag to determine whether the situation is diner situation.\n        ', tunable_type=Tag, filter_prefixes=['situation'], default=Tag.INVALID, invalid_enums=(Tag.INVALID,), pack_safe=True)
예제 #19
0
class Business(HasTunableReference,
               metaclass=HashedTunedInstanceMetaclass,
               manager=services.get_instance_manager(
                   sims4.resources.Types.BUSINESS)):
    INSTANCE_TUNABLES = {
        'employee_data_map':
        TunableMapping(
            description=
            '\n            The mapping between Business Employee Type and the Employee Data for\n            that type.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The Business Employee Type that should get the specified Career.\n                ',
                tunable_type=BusinessEmployeeType,
                default=BusinessEmployeeType.INVALID,
                invalid_enums=(BusinessEmployeeType.INVALID, )),
            value_type=TunableBusinessEmployeeDataSnippet(
                description=
                '\n                The Employee Data for the given Business Employee Type.\n                '
            ),
            tuning_group=GroupNames.EMPLOYEES),
        'npc_starting_funds':
        TunableRange(
            description=
            '\n            The amount of money an npc-owned store will start with in their\n            funds. Typically should be set to the same cost as the interaction\n            to buy the business.\n            ',
            tunable_type=int,
            default=0,
            minimum=0,
            tuning_group=GroupNames.CURRENCY),
        'funds_category_data':
        TunableMapping(
            description=
            '\n            Data associated with specific business funds categories.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The funds category.\n                ',
                tunable_type=BusinessFundsCategory,
                default=BusinessFundsCategory.NONE,
                invalid_enums=(BusinessFundsCategory.NONE, )),
            value_type=TunableTuple(
                description=
                '\n                The data associated with this retail funds category.\n                ',
                summary_dialog_entry=OptionalTunable(
                    description=
                    "\n                    If enabled, an entry for this category is displayed in the\n                    business' summary dialog.\n                    ",
                    tunable=TunableLocalizedString(
                        description=
                        '\n                        The dialog entry for this retail funds category. This\n                        string takes no tokens.\n                        '
                    ))),
            tuning_group=GroupNames.CURRENCY),
        'default_markup_multiplier':
        TunableRange(
            description=
            '\n            The default markup multiplier for a new business. This must match a\n            multiplier that\'s in the Markup Multiplier Data tunable. It\'s also\n            possible for this to be less than 1, meaning the default "markup"\n            will actually cause prices to be lower than normal.\n            ',
            tunable_type=float,
            default=1.25,
            minimum=math.EPSILON,
            tuning_group=GroupNames.CURRENCY),
        'advertising_name_map':
        TunableMapping(
            description=
            '\n            The mapping between advertising enum and the name used in the UI for\n            that type.\n            ',
            key_name='advertising_enum',
            key_type=TunableEnumEntry(
                description=
                '\n                The Advertising Type.\n                ',
                tunable_type=BusinessAdvertisingType,
                default=BusinessAdvertisingType.INVALID,
                invalid_enums=(BusinessAdvertisingType.INVALID, ),
                binary_type=EnumBinaryExportType.EnumUint32),
            value_name='advertising_name',
            value_type=TunableLocalizedString(
                description=
                '\n                The name of the advertising type used in the UI.\n                '
            ),
            tuple_name='AdvertisingEnumDataMappingTuple',
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.All),
        'advertising_type_sort_order':
        TunableList(
            description=
            '\n            Sort order for the advertising types in the UI\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The Advertising Type.\n                ',
                tunable_type=BusinessAdvertisingType,
                default=BusinessAdvertisingType.INVALID,
                invalid_enums=(BusinessAdvertisingType.INVALID, ),
                binary_type=EnumBinaryExportType.EnumUint32),
            unique_entries=True,
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.All),
        'quality_settings':
        TunableList(
            description=
            '\n            Tunable Business quality settings.  \n            \n            Quality type can be interpreted in different ways \n            by specific businesses, and can be used for tests.\n            \n            These are quality settings that are exported to the client.\n            \n            The order in this list should be the order we want them displayed\n            in the UI.\n            ',
            tunable=TunableTuple(
                quality_type=TunableEnumEntry(
                    description=
                    '\n                    The quality Type.\n                    ',
                    tunable_type=BusinessQualityType,
                    default=BusinessQualityType.INVALID,
                    invalid_enums=(BusinessQualityType.INVALID, ),
                    binary_type=EnumBinaryExportType.EnumUint32),
                quality_name=TunableLocalizedString(
                    description=
                    '\n                    The name of the quality type used in the UI.\n                    '
                ),
                export_class_name='QualitySettingsData'),
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.All),
        'show_settings_button':
        OptionalTunable(
            description=
            "\n            If enabled, this business type will show the settings button with \n            the tuned tooltip text. If disabled, this business type won't show\n            the settings button.\n            ",
            tunable=TunableLocalizedString(
                description=
                '\n                The tooltip to show on the settings button when it is shown\n                for this business type.\n                '
            ),
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.ClientBinary),
        'business_summary_tooltip':
        OptionalTunable(
            description=
            '\n            If enabled, allows tuning a business summary tooltip. If disabled, no\n            tooltip will be used or displayed by the UI.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                The tooltip to show on the business panel.\n                '
            ),
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.ClientBinary),
        'show_sell_button':
        Tunable(
            description=
            "\n            If checked, the sell button will be shown in the business panel if\n            the business is on the active lot. If left unchecked, the sell button\n            won't be shown on the business panel at all.\n            ",
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.ClientBinary),
        'show_employee_button':
        Tunable(description='\n            ',
                tunable_type=bool,
                default=False,
                tuning_group=GroupNames.UI,
                export_modes=ExportModes.ClientBinary),
        'default_quality':
        OptionalTunable(
            description=
            '\n            The default quality type for the business.',
            tunable=TunableEnumEntry(
                tunable_type=BusinessQualityType,
                default=BusinessQualityType.INVALID,
                invalid_enums=(BusinessQualityType.INVALID, ),
                binary_type=EnumBinaryExportType.EnumUint32),
            disabled_value=BusinessQualityType.INVALID,
            tuning_group=GroupNames.BUSINESS),
        'quality_unlock_perk':
        OptionalTunable(
            description=
            '\n            Reference to a perk that, if unlocked, allow the player to adjust\n            the quality type specific to this business.\n            ',
            tunable=TunablePackSafeReference(
                manager=services.get_instance_manager(
                    sims4.resources.Types.BUCKS_PERK)),
            tuning_group=GroupNames.BUSINESS),
        'advertising_configuration':
        AdvertisingConfiguration.TunableFactory(
            description=
            '\n            Tunable Business advertising configuration.\n            ',
            tuning_group=GroupNames.BUSINESS),
        'markup_multiplier_data':
        TunableList(
            description=
            '\n            A list of markup multiplier display names and the actual multiplier\n            associated with that name. This is used for sending the markup\n            information to the UI.\n            ',
            tunable=TunableTuple(
                description=
                '\n               A tuple of the markup multiplier display name and the actual\n               multiplier associated with that display name.\n               ',
                name=TunableLocalizedString(
                    description=
                    '\n                   The display name for this markup multiplier. e.g. a\n                   multiplier of 1.2 will have "20 %" tuned here.\n                   '
                ),
                markup_multiplier=TunableRange(
                    description=
                    '\n                    The multiplier associated with this display name.\n                    ',
                    tunable_type=float,
                    default=1,
                    minimum=math.EPSILON),
                export_class_name='MarkupMultiplierData'),
            tuning_group=GroupNames.CURRENCY,
            export_modes=ExportModes.All),
        'star_rating_to_screen_slam_map':
        TunableMapping(
            description=
            '\n            A mapping of star ratings to screen slams.\n            Screen slams will be triggered when the rating increases to a new\n            whole value.\n            ',
            key_type=int,
            value_type=ui.screen_slam.TunableScreenSlamSnippet(),
            key_name='star_rating',
            value_name='screen_slam',
            tuning_group=GroupNames.BUSINESS),
        'show_empolyee_skill_level_up_notification':
        Tunable(
            description=
            '\n            If true, skill level up notifications will be shown for employees.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.EMPLOYEES),
        'bucks':
        TunableEnumEntry(
            description=
            '\n            The Bucks Type this business will use for Perk unlocks.\n            ',
            tunable_type=BucksType,
            default=BucksType.INVALID,
            invalid_enums=(BucksType.INVALID, ),
            tuning_group=GroupNames.CURRENCY,
            export_modes=ExportModes.All),
        'off_lot_star_rating_decay_multiplier_perk':
        OptionalTunable(
            description=
            '\n            If enabled, allows the tuning of a perk which can adjust the off-lot star rating decay.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The off lot star rating decay multiplier tuning.\n                ',
                perk=TunableReference(
                    description=
                    '\n                    The perk that will cause the multiplier to be applied to the\n                    star rating decay during off-lot simulations.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.BUCKS_PERK)),
                decay_multiplier=TunableRange(
                    description=
                    '\n                    If the household has the specified perk, the off-lot star\n                    rating decay rate will be multiplied by this value.\n                    ',
                    tunable_type=float,
                    default=1.1,
                    minimum=0)),
            tuning_group=GroupNames.OFF_LOT),
        'manage_outfit_affordances':
        TunableSet(
            description=
            '\n            A list of affordances that are shown when the player clicks on the\n            Manage Outfits button.\n            ',
            tunable=TunableReference(
                description=
                '\n                An affordance shown when the player clicks on the Manage Outfits\n                button.\n                ',
                manager=services.affordance_manager(),
                pack_safe=True),
            tuning_group=GroupNames.EMPLOYEES),
        'employee_training_buff_tag':
        TunableEnumWithFilter(
            description=
            '\n            A tag to indicate a buff is used for employee training.\n            ',
            tunable_type=tag.Tag,
            default=tag.Tag.INVALID,
            invalid_enums=(tag.Tag.INVALID, ),
            filter_prefixes=('buff', ),
            pack_safe=True,
            tuning_group=GroupNames.EMPLOYEES),
        'customer_buffs_to_save_tag':
        TunableEnumWithFilter(
            description=
            '\n            All buffs with this tag will be saved and reapplied to customer sims\n            on load.\n            ',
            tunable_type=tag.Tag,
            default=tag.Tag.INVALID,
            invalid_enums=(tag.Tag.INVALID, ),
            filter_prefixes=('buff', ),
            pack_safe=True,
            tuning_group=GroupNames.CUSTOMER),
        'customer_buffs_to_remove_tags':
        TunableSet(
            description=
            '\n            Tags that indicate which buffs should be removed from customers when\n            they leave the business.\n            ',
            tunable=TunableEnumWithFilter(
                description=
                '\n                A tag that indicates a buff should be removed from the customer\n                when they leave the business.\n                ',
                tunable_type=tag.Tag,
                default=tag.Tag.INVALID,
                invalid_enums=(tag.Tag.INVALID, ),
                filter_prefixes=('buff', ),
                pack_safe=True),
            tuning_group=GroupNames.CUSTOMER),
        'current_business_lot_transfer_dialog_entry':
        TunableLocalizedString(
            description=
            '\n            This is the text that will show in the funds transfer dialog drop\n            down for the current lot if it\'s a business lot. Typically, the lot\n            name would show but if the active lot is a business lot it makes\n            more sense to say something along the lines of\n            "Current Retail Lot" or "Current Restaurant" instead of the name of the lot.\n            ',
            tuning_group=GroupNames.UI),
        'open_business_notification':
        TunableUiDialogNotificationSnippet(
            description=
            '\n            The notification that shows up when the player opens the business.\n            We need to trigger this from code because we need the notification\n            to show up when we open the store through the UI or through an\n            Interaction.\n            ',
            tuning_group=GroupNames.UI),
        'no_way_to_make_money_notification':
        TunableUiDialogNotificationSnippet(
            description=
            '\n            The notification that shows up when the player opens a store that has no\n            way of currently making money (e.g. retail store having no items set for\n            sale or restaurants having nothing on the menu). It will replace the\n            Open Business Notification.\n            ',
            tuning_group=GroupNames.UI),
        'audio_sting_open':
        TunablePlayAudioAllPacks(
            description=
            '\n            The audio sting to play when the store opens.\n            ',
            tuning_group=GroupNames.UI),
        'audio_sting_close':
        TunablePlayAudioAllPacks(
            description=
            '\n            The audio sting to play when the store closes.\n            ',
            tuning_group=GroupNames.UI),
        'sell_store_dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            This dialog is to confirm the sale of the business.\n            ',
            tuning_group=GroupNames.UI),
        'lighting_helper_open':
        LightingHelper.TunableFactory(
            description=
            '\n            The lighting helper to execute when the store opens.\n            e.g. Turn on all neon signs.\n            ',
            tuning_group=GroupNames.TRIGGERS),
        'lighting_helper_close':
        LightingHelper.TunableFactory(
            description=
            '\n            The lighting helper to execute when the store closes.\n            e.g. Turn off all neon signs.\n            ',
            tuning_group=GroupNames.TRIGGERS),
        'min_and_max_star_rating':
        TunableInterval(
            description=
            '\n            The lower and upper bounds for a star rating. This affects both the\n            customer star rating and the overall business star rating.\n            ',
            tunable_type=float,
            default_lower=1,
            default_upper=5,
            tuning_group=GroupNames.BUSINESS),
        'min_and_max_star_rating_value':
        TunableInterval(
            description=
            '\n            The minimum and maximum star rating value for this business.\n            ',
            tunable_type=float,
            default_lower=0,
            default_upper=100,
            tuning_group=GroupNames.BUSINESS),
        'star_rating_value_to_user_facing_rating_curve':
        TunableCurve(
            description=
            '\n           Curve that maps star rating values to the user-facing star rating.\n           ',
            x_axis_name='Star Rating Value',
            y_axis_name='User-Facing Star Rating',
            tuning_group=GroupNames.BUSINESS),
        'default_business_star_rating_value':
        TunableRange(
            description=
            '\n            The star rating value a newly opened business will begin with. Keep in mind, this is not the actual star rating. This is the value which maps to a rating using \n            ',
            tunable_type=float,
            default=1,
            minimum=0,
            tuning_group=GroupNames.BUSINESS),
        'customer_rating_delta_to_business_star_rating_value_change_curve':
        TunableCurve(
            description=
            '\n            When a customer is done with their meal, we will take the delta\n            between their rating and the business rating and map that to an\n            amount it should change the star rating value for the restaurant.\n            \n            For instance, the business has a current rating of 3 stars and the\n            customer is giving a rating of 4.5 stars. 4.5 - 3 = a delta of 1.5.\n            That 1.5 will map, on this curve, to the amount we should adjust the\n            star rating value for the business.\n            ',
            x_axis_name=
            'Customer Rating to Business Rating Delta (restaurant rating - customer rating)',
            y_axis_name='Business Star Rating Value Change',
            tuning_group=GroupNames.BUSINESS),
        'default_customer_star_rating':
        TunableRange(
            description=
            '\n            The star rating a new customer starts with.\n            ',
            tunable_type=float,
            default=3,
            minimum=0,
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_rating_buff_bucket_data':
        TunableMapping(
            description=
            "\n            A mapping from Business Customer Star Rating Buff Bucket to the data\n            associated with the buff bucker for this business.\n            \n            Each buff bucket has a minimum, median, and maximum value. For every\n            buff a customer has that falls within a buff bucket, that buff's\n            Buff Bucket Delta is added to that bucket's totals. The totals are\n            clamped between -1 and 1 and interpolated against the\n            minimum/medium/maximum value for their associated buckets. All of\n            the final values of the buckets are added together and that value is\n            used in the Customer Star Buff Bucket To Rating Curve to determine\n            the customer's final star rating of this business.\n            \n            For instance, assume a buff bucket has a minimum value of -200, median of 0,\n            and maximum of 100, and the buff bucket's clamped total is 0.5, the actual\n            value of that bucket will be 50 (half way, or 0.5, between 0 and\n            100). If, however, the bucket's total is -0.5, we'd interpolate\n            between the bucket's minimum value, -200, and median value, 0, to arrive at a\n            bucket value of -100.\n            ",
            key_name='Star_Rating_Buff_Bucket',
            key_type=TunableEnumEntry(
                description=
                '\n                The Business Customer Star Rating Buff Bucket enum.\n                ',
                tunable_type=BusinessCustomerStarRatingBuffBuckets,
                default=BusinessCustomerStarRatingBuffBuckets.INVALID,
                invalid_enums=(
                    BusinessCustomerStarRatingBuffBuckets.INVALID, )),
            value_name='Star_Rating_Buff_Bucket_Data',
            value_type=TunableTuple(
                description=
                '\n                All of the data associated with a specific customer star rating\n                buff bucket.\n                ',
                bucket_value_minimum=Tunable(
                    description=
                    "\n                    The minimum value for this bucket's values.\n                    ",
                    tunable_type=float,
                    default=-100),
                positive_bucket_vfx=PlayEffect.TunableFactory(
                    description=
                    '\n                    The vfx to play when positive change star value occurs. \n                    '
                ),
                negative_bucket_vfx=PlayEffect.TunableFactory(
                    description=
                    '\n                    The vfx to play when negative change star value occurs.\n                    '
                ),
                bucket_value_median=Tunable(
                    description=
                    "\n                    The median/middle value for this bucket's values.\n                    ",
                    tunable_type=float,
                    default=0),
                bucket_value_maximum=Tunable(
                    description=
                    "\n                    The maximum value for this bucket's values.\n                    ",
                    tunable_type=float,
                    default=100),
                bucket_icon=TunableIconAllPacks(
                    description=
                    '\n                    The icon that represents this buff bucket.\n                    '
                ),
                bucket_positive_text=TunableLocalizedStringFactoryVariant(
                    description=
                    '\n                    The possible text strings to show up when this bucket\n                    results in a positive star rating.\n                    '
                ),
                bucket_negative_text=TunableLocalizedStringFactoryVariant(
                    description=
                    '\n                    The possible text strings to show up when this bucket\n                    results in a bad star rating.\n                    '
                ),
                bucket_excellence_text=TunableLocalizedStringFactoryVariant(
                    description=
                    "\n                    The description text to use in the business summary panel if\n                    this buff bucket is in the 'Excellence' section.\n                    "
                ),
                bucket_growth_opportunity_text=
                TunableLocalizedStringFactoryVariant(
                    description=
                    "\n                    The description text to use in the business summary panel if\n                    this buff bucket is in the 'Growth Opportunity' section.\n                    "
                ),
                bucket_growth_opportunity_threshold=TunableRange(
                    description=
                    '\n                    The amount of score this bucket must be from the maximum to be\n                    considered a growth opportunity. \n                    ',
                    tunable_type=float,
                    minimum=0,
                    default=10),
                bucket_excellence_threshold=TunableRange(
                    description=
                    '\n                    The amount of score this bucket must be before it is \n                    considered an excellent bucket\n                    ',
                    tunable_type=float,
                    minimum=0,
                    default=1),
                bucket_title=TunableLocalizedString(
                    description=
                    '\n                    The name for this bucket.\n                    '
                )),
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_rating_buff_data':
        TunableMapping(
            description=
            '\n            A mapping of Buff to the buff data associated with that buff.\n            \n            Refer to the description on Customer Star Rating Buff Bucket Data\n            for a detailed explanation of how this tuning works.\n            ',
            key_name='Buff',
            key_type=Buff.TunableReference(
                description=
                "\n                A buff meant to drive a customer's star rating for a business.\n                ",
                pack_safe=True),
            value_name='Buff Data',
            value_type=TunableTuple(
                description=
                '\n                The customer star rating for this buff.\n                ',
                buff_bucket=TunableEnumEntry(
                    description=
                    '\n                    The customer star rating buff bucket associated with this buff.\n                    ',
                    tunable_type=BusinessCustomerStarRatingBuffBuckets,
                    default=BusinessCustomerStarRatingBuffBuckets.INVALID,
                    invalid_enums=(
                        BusinessCustomerStarRatingBuffBuckets.INVALID, )),
                buff_bucket_delta=Tunable(
                    description=
                    '\n                    The amount of change this buff should contribute to its bucket.\n                    ',
                    tunable_type=float,
                    default=0),
                update_star_rating_on_add=Tunable(
                    description=
                    "\n                    If enabled, the customer's star rating will be re-\n                    calculated when this buff is added.\n                    ",
                    tunable_type=bool,
                    default=True),
                update_star_rating_on_remove=Tunable(
                    description=
                    "\n                    If enabled, the customer's star rating will be re-\n                    calculated when this buff is removed.\n                    ",
                    tunable_type=bool,
                    default=False)),
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_buff_bucket_to_rating_curve':
        TunableCurve(
            description=
            '\n            A mapping of the sum of all buff buckets for a single customer to\n            the star rating for that customer.\n            \n            Refer to the description on Customer Star Rating Buff Bucket Data\n            for a detailed explanation of how this tuning works.\n            ',
            x_axis_name='Buff Bucket Total',
            y_axis_name='Star Rating',
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_rating_vfx_increase_arrow':
        OptionalTunable(
            description=
            '\n            The "up arrow" VFX to play when a customer\'s star rating goes up.\n            These will play even if the customer\'s rating doesn\'t go up enough\n            to trigger a star change.\n            ',
            tunable=PlayEffect.TunableFactory(),
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_rating_vfx_decrease_arrow':
        OptionalTunable(
            description=
            '\n            The "down arrow" VFX to play when a customer\'s star rating goes\n            down. These will play even if the customer\'s rating doesn\'t go down\n            enough to trigger a star change.\n            ',
            tunable=PlayEffect.TunableFactory(),
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_rating_vfx_mapping':
        TunableStarRatingVfxMapping(
            description=
            '\n            Maps the star rating for the customer to the persistent star effect\n            that shows over their head.\n            ',
            tuning_group=GroupNames.CUSTOMER),
        'customer_final_star_rating_vfx':
        OptionalTunable(
            description=
            '\n            The VFX to play when the customer is done and is submitting their\n            final star rating to the business.\n            ',
            tunable=PlayEffect.TunableFactory(),
            tuning_group=GroupNames.CUSTOMER),
        'customer_max_star_rating_vfx':
        OptionalTunable(
            description=
            '\n            The VFX to play when the customer hits the maximum star rating.\n            ',
            tunable=PlayEffect.TunableFactory(),
            tuning_group=GroupNames.CUSTOMER),
        'customer_star_rating_statistic':
        TunablePackSafeReference(
            description=
            '\n            The statistic on a customer Sim that represents their current star\n            rating.\n            ',
            manager=services.get_instance_manager(Types.STATISTIC),
            allow_none=True,
            tuning_group=GroupNames.CUSTOMER),
        'buy_business_lot_affordance':
        TunableReference(
            description=
            '\n            The affordance to buy a lot for this type of business.\n            ',
            manager=services.get_instance_manager(Types.INTERACTION),
            tuning_group=GroupNames.UI),
        'initial_funds_transfer_amount':
        TunableRange(
            description=
            '\n            The amount to default the funds transfer dialog when a player\n            initially buys this business.\n            ',
            tunable_type=int,
            minimum=0,
            default=2500,
            tuning_group=GroupNames.CURRENCY),
        'summary_dialog_icon':
        TunableIcon(
            description=
            '\n            The Icon to show in the header of the dialog.\n            ',
            tuning_group=GroupNames.UI),
        'summary_dialog_subtitle':
        TunableLocalizedString(
            description=
            "\n            The subtitle for the dialog. The main title will be the store's name.\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_transactions_header':
        TunableLocalizedString(
            description=
            "\n            The header for the 'Items Sold' line item. By design, this should say\n            something along the lines of 'Items Sold:' or 'Transactions:'\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_transactions_text':
        TunableLocalizedStringFactory(
            description=
            "\n            The text in the 'Items Sold' line item. By design, this should say\n            the number of items sold.\n            {0.Number} = number of items sold since the store was open\n            i.e. {0.Number}\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_cost_of_ingredients_header':
        TunableLocalizedString(
            description=
            "\n            The header for the 'Cost of Ingredients' line item. By design, this\n            should say something along the lines of 'Cost of Ingredients:'\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_cost_of_ingredients_text':
        TunableLocalizedStringFactory(
            description=
            "\n            The text in the 'Cost of Ingredients' line item. {0.Number} = the\n            amount of money spent on ingredients.\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_food_profit_header':
        TunableLocalizedString(
            description=
            "\n            The header for the 'Food Profits' line item. This line item is the\n            total revenue minus the cost of ingredients. By design, this should\n            say something along the lines of 'Food Profits:'\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_food_profit_text':
        TunableLocalizedStringFactory(
            description=
            "\n            The text in the 'Food Profits' line item. {0.Number} = the amount of\n            money made on food.\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_wages_owed_header':
        TunableLocalizedString(
            description=
            "\n            The header text for the 'Wages Owned' line item. By design, this\n            should say 'Wages Owed:'\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_wages_owed_text':
        TunableLocalizedStringFactory(
            description=
            "\n            The text in the 'Wages Owed' line item. By design, this should say the\n            number of hours worked and the price per hour.\n            {0.Number} = number of hours worked by all employees\n            {1.Money} = amount employees get paid per hour\n            i.e. {0.Number} hours worked x {1.Money}/hr\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_payroll_header':
        TunableLocalizedStringFactory(
            description=
            '\n            The header text for each unique Sim on payroll. This is provided one\n            token, the Sim.\n            ',
            tuning_group=GroupNames.UI),
        'summary_dialog_payroll_text':
        TunableLocalizedStringFactory(
            description=
            '\n            The text for each job that the Sim on payroll has held today. This is\n            provided three tokens: the career level name, the career level salary,\n            and the total hours worked.\n            \n            e.g.\n             {0.String} ({1.Money}/hr) * {2.Number} {S2.hour}{P2.hours}\n            ',
            tuning_group=GroupNames.UI),
        'summary_dialog_wages_advertising_header':
        TunableLocalizedString(
            description=
            "\n            The header text for the 'Advertising' line item. By design, this\n            should say 'Advertising Spent:'\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_wages_advertising_text':
        TunableLocalizedStringFactory(
            description=
            "\n            The text in the 'Advertising' line item. By design, this should say the\n            amount spent on advertising\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_wages_net_profit_header':
        TunableLocalizedString(
            description=
            "\n            The header text for the 'Net Profit' line item. By design, this\n            should say 'Net Profit:'\n            ",
            tuning_group=GroupNames.UI),
        'summary_dialog_wages_net_profit_text':
        TunableLocalizedStringFactory(
            description=
            "\n            The text in the 'Net Profit' line item. By design, this should say the\n            total amount earnt so far in this shift\n            ",
            tuning_group=GroupNames.UI),
        'grand_opening_notification':
        OptionalTunable(
            description=
            '\n            If enabled, allows a notification to be tuned that will show only\n            the first time you arrive on your business lot.\n            ',
            tunable=TunableUiDialogNotificationSnippet(),
            tuning_group=GroupNames.UI),
        'business_icon':
        TunableIcon(
            description=
            '\n            The Icon to show in the header of the dialog.\n            ',
            tuning_group=GroupNames.UI),
        'star_rating_to_customer_count_curve':
        TunableCurve(
            description=
            '\n            A curve mapping of current star rating of the restaurant to the base\n            number of customers that should come per interval.\n            ',
            x_axis_name='Star Rating',
            y_axis_name='Base Customer Count',
            tuning_group=GroupNames.CUSTOMER),
        'time_of_day_to_customer_count_multiplier_curve':
        TunableCurve(
            description=
            '\n            A curve that lets you tune a specific customer multiplier based on the \n            time of day. \n            \n            Time of day should range between 0 and 23, 0 being midnight.\n            ',
            tuning_group=GroupNames.CUSTOMER,
            x_axis_name='time_of_day',
            y_axis_name='customer_multiplier'),
        'off_lot_customer_count_multiplier':
        TunableRange(
            description=
            '\n            This value will be multiplied by the Base Customer Count (derived\n            from the Star Rating To Customer Count Curve) to determine the base\n            number of customers per hour during off-lot simulation.\n            ',
            tunable_type=float,
            minimum=0,
            default=0.5,
            tuning_group=GroupNames.OFF_LOT),
        'off_lot_customer_count_penalty_multiplier':
        TunableRange(
            description=
            '\n            A penalty multiplier applied to the off-lot customer count. This is\n            applied after the Off Lot Customer Count Multiplier is applied.\n            ',
            tunable_type=float,
            default=0.2,
            minimum=0,
            tuning_group=GroupNames.OFF_LOT),
        'off_lot_chance_of_star_rating_increase':
        TunableRange(
            description=
            "\n            Every time we run offlot simulations, we'll use this as the chance\n            to increase in star rating instead of decrease.\n            ",
            tunable_type=float,
            default=0.1,
            minimum=0,
            tuning_group=GroupNames.OFF_LOT),
        'off_lot_star_rating_decay_per_hour_curve':
        TunableCurve(
            description=
            '\n            Maps the current star rating of the business to the decay per hour\n            of star rating value. This value will be added to the current star\n            rating value so use negative numbers to make the rating decay.\n            ',
            x_axis_name='Business Star Rating',
            y_axis_name='Off-Lot Star Rating Value Decay Per Hour',
            tuning_group=GroupNames.OFF_LOT),
        'off_lot_star_rating_increase_per_hour_curve':
        TunableCurve(
            description=
            '\n            Maps the current star rating of the business to the increase per\n            hour of the star rating value, assuming the Off Lot Chance Of Star\n            Rating Increase passes.\n            ',
            x_axis_name='Business Star Rating',
            y_axis_name='Off-Lot Star Rating Value Increase Per Hour',
            tuning_group=GroupNames.OFF_LOT),
        'off_lot_profit_per_item_multiplier':
        TunableRange(
            description=
            '\n            This is multiplied by the average cost of the business specific\n            service that is the main source of profit, to determine how much \n            money the business makes per customer during off-lot simulation.\n            ',
            tunable_type=float,
            default=0.3,
            minimum=0,
            tuning_group=GroupNames.OFF_LOT),
        'off_lot_net_loss_notification':
        OptionalTunable(
            description=
            '\n            If enabled, the notification that will show if a business turns a \n            negative net profit during off-lot simulation.\n            ',
            tunable=TunableUiDialogNotificationSnippet(),
            tuning_group=GroupNames.OFF_LOT),
        'critic':
        OptionalTunable(
            description=
            '\n            If enabled, allows tuning a critic for this business type.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Critic tuning for this business.\n                ',
                critic_trait=TunableReference(
                    description=
                    '\n                    The trait used to identify a critic of this business.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.TRAIT)),
                critic_star_rating_application_count=TunableRange(
                    description=
                    '\n                    The number of times a critics star rating should count towards the\n                    business star rating.\n                    ',
                    tunable_type=int,
                    default=10,
                    minimum=1),
                critic_star_rating_vfx_mapping=TunableStarRatingVfxMapping(
                    description=
                    '\n                    Maps the star rating for the critic to the persistent star effect\n                    that shows over their head.\n                    '
                ),
                critic_banner_vfx=PlayEffect.TunableFactory(
                    description=
                    '\n                    A persistent banner VFX that is started when the critic\n                    arrives and stopped when they leave.\n                    '
                )),
            tuning_group=GroupNames.CUSTOMER)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        advertising_data_types = frozenset(
            cls.advertising_configuration.advertising_data_map.keys())
        advertising_types_with_mapped_names = frozenset(
            cls.advertising_name_map.keys())
        advertising_sort_ordered_types = frozenset(
            cls.advertising_name_map.keys())
        if advertising_data_types:
            if advertising_data_types != advertising_types_with_mapped_names:
                logger.error(
                    'Advertising type list {} does not match list of mapped names: {}',
                    advertising_data_types,
                    advertising_types_with_mapped_names)
            if advertising_data_types != advertising_sort_ordered_types:
                logger.error(
                    'Advertising type list {} does not sorted UI list types: {}',
                    advertising_data_types, advertising_sort_ordered_types)
        if cls.advertising_configuration.default_advertising_type is not None and cls.advertising_configuration.default_advertising_type not in advertising_types_with_mapped_names:
            logger.error(
                'Default advertising type {} is not in advertising name map',
                cls.default_advertising_type)
예제 #20
0
class ObjectManager(DistributableObjectManager, GameObjectManagerMixin,
                    AttractorManagerMixin):
    FIREMETER_DISPOSABLE_OBJECT_CAP = Tunable(
        int,
        5,
        description=
        'Number of disposable objects a lot can have at any given moment.')
    BED_TAGS = TunableTuple(
        description=
        '\n        Tags to check on an object to determine what type of bed an object is.\n        ',
        beds=TunableSet(
            description=
            '\n            Tags that consider an object as a bed other than double beds.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        double_beds=TunableSet(
            description=
            '\n            Tags that consider an object as a double bed\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        kid_beds=TunableSet(
            description=
            '\n            Tags that consider an object as a kid bed\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        other_sleeping_spots=TunableSet(
            description=
            '\n            Tags that considered sleeping spots.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)))
    HOUSEHOLD_INVENTORY_OBJECT_TAGS = TunableTags(
        description=
        '\n        List of tags to apply to every household inventory proxy object.\n        '
    )
    INVALID_UNPARENTED_OBJECT_TAGS = TunableTags(
        description=
        '\n        Objects with these tags should not exist without a parent. An obvious\n        case is for transient objects. They should only exist as a carried object,\n        thus parented to a sim, when loading into a save game.\n        '
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._crafting_cache = CraftingObjectCache()
        self._sim_spawn_conditions = collections.defaultdict(set)
        self._water_terrain_object_cache = WaterTerrainObjectCache()
        self._client_connect_callbacks = CallableList()
        self._portal_cache = WeakSet()
        self._portal_added_callbacks = CallableList()
        self._portal_removed_callbacks = CallableList()
        self._front_door_candidates_changed_callback = CallableList()
        self._all_bed_tags = self.BED_TAGS.beds | self.BED_TAGS.double_beds | self.BED_TAGS.kid_beds | self.BED_TAGS.other_sleeping_spots
        self._tag_to_object_list = defaultdict(set)
        self._whim_set_cache = Counter()
        self._posture_providing_object_cache = None
        self._objects_to_ignore_portal_validation_cache = []

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

    @property
    def crafting_cache(self):
        return self._crafting_cache

    @property
    def water_terrain_object_cache(self):
        return self._water_terrain_object_cache

    def portal_cache_gen(self):
        yield from self._portal_cache

    def on_client_connect(self, client):
        all_objects = list(self._objects.values())
        for game_object in all_objects:
            game_object.on_client_connect(client)

    def move_to_inventory(self, obj, inventory_manager):
        logger.assert_raise(
            isinstance(inventory_manager, InventoryManager),
            'Trying to move object to a non-inventory manager: {}',
            inventory_manager,
            owner='tingyul')
        logger.assert_raise(
            obj.id,
            'Attempting to move an object that was never added or has already been removed',
            owner='tingyul')
        logger.assert_raise(
            self._objects.get(obj.id) is obj,
            'Attempting to move an object {} that is not in this manager or not the same object {} in manager',
            obj,
            self._objects.get(obj.id),
            owner='tingyul')
        del self._objects[obj.id]
        obj.manager = inventory_manager
        inventory_manager._objects[obj.id] = obj
        self.remove_object_from_object_tags_cache(obj)
        self.remove_object_from_posture_providing_cache(obj)

    def add(self, obj, *args, **kwargs):
        super().add(obj, *args, **kwargs)
        self.add_object_to_object_tags_cache(obj)
        self.add_object_to_posture_providing_cache(obj)

    def remove(self, obj, *args, **kwargs):
        super().remove(obj, *args, **kwargs)
        current_zone = services.current_zone()
        if not current_zone.is_zone_shutting_down:
            self.remove_object_from_object_tags_cache(obj)
            self.remove_object_from_posture_providing_cache(obj)

    def add_object_to_object_tags_cache(self, obj):
        self.add_tags_and_object_to_cache(obj.get_tags(), obj)

    def add_tags_and_object_to_cache(self, tags, obj):
        if obj.id not in self:
            logger.error(
                "Trying to add object to tag cache when the object isn't in the manager: {}",
                obj,
                owner='tingyul')
            return
        for tag in tags:
            object_list = self._tag_to_object_list[tag]
            object_list.add(obj)

    def remove_object_from_object_tags_cache(self, obj):
        for tag in obj.get_tags():
            if tag not in self._tag_to_object_list:
                continue
            object_list = self._tag_to_object_list[tag]
            if obj not in object_list:
                continue
            object_list.remove(obj)
            if not object_list:
                del self._tag_to_object_list[tag]

    def _should_save_object_on_lot(self, obj):
        parent = obj.parent
        if parent is not None and parent.is_sim:
            inventory = parent.inventory_component
            if inventory.should_save_parented_item_to_inventory(obj):
                return False
            else:
                vehicle_component = obj.vehicle_component
                if vehicle_component is not None:
                    driver = vehicle_component.driver
                    if driver is not None and driver.is_sim:
                        inventory = driver.inventory_component
                        if inventory.should_save_parented_item_to_inventory(
                                obj):
                            return False
        else:
            vehicle_component = obj.vehicle_component
            if vehicle_component is not None:
                driver = vehicle_component.driver
                if driver is not None and driver.is_sim:
                    inventory = driver.inventory_component
                    if inventory.should_save_parented_item_to_inventory(obj):
                        return False
        return True

    def add_object_to_posture_providing_cache(self, obj):
        if not obj.provided_mobile_posture_affordances:
            return
        if self._posture_providing_object_cache is None:
            self._posture_providing_object_cache = set()
        self._posture_providing_object_cache.add(obj)
        posture_graph_service = services.posture_graph_service()
        if not posture_graph_service.has_built_for_zone_spin_up:
            posture_graph_service.on_mobile_posture_object_added_during_zone_spinup(
                obj)

    def remove_object_from_posture_providing_cache(self, obj):
        if not obj.provided_mobile_posture_affordances:
            return
        self._posture_providing_object_cache.remove(obj)
        if not self._posture_providing_object_cache:
            self._posture_providing_object_cache = None

    def get_posture_providing_objects(self):
        return self._posture_providing_object_cache or ()

    def rebuild_objects_to_ignore_portal_validation_cache(self):
        self._objects_to_ignore_portal_validation_cache.clear()
        for obj in self._objects.values():
            if not obj.routing_component is not None:
                if not obj.inventoryitem_component is not None:
                    if obj.live_drag_component is not None:
                        self._objects_to_ignore_portal_validation_cache.append(
                            obj.id)
            self._objects_to_ignore_portal_validation_cache.append(obj.id)

    def clear_objects_to_ignore_portal_validation_cache(self):
        self._objects_to_ignore_portal_validation_cache.clear()

    def get_objects_to_ignore_portal_validation_cache(self):
        return self._objects_to_ignore_portal_validation_cache

    def clear_caches_on_teardown(self):
        self._tag_to_object_list.clear()
        self._water_terrain_object_cache.clear()
        if self._posture_providing_object_cache is not None:
            self._posture_providing_object_cache.clear()
        self.clear_objects_to_ignore_portal_validation_cache()
        build_buy.unregister_build_buy_exit_callback(
            self._water_terrain_object_cache.refresh)

    def pre_save(self):
        all_objects = list(self._objects.values())
        lot = services.current_zone().lot
        for (_, inventory) in lot.get_all_object_inventories_gen(
                shared_only=True):
            for game_object in inventory:
                all_objects.append(game_object)
        for game_object in all_objects:
            game_object.update_all_commodities()

    @staticmethod
    def save_game_object(game_object, object_list, open_street_objects):
        save_result = None
        if game_object.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT:
            save_result = game_object.save_object(object_list.objects,
                                                  ItemLocation.ON_LOT, 0)
        else:
            if game_object.item_location == ItemLocation.ON_LOT or game_object.item_location == ItemLocation.INVALID_LOCATION:
                item_location = ItemLocation.FROM_OPEN_STREET
            else:
                item_location = game_object.item_location
            save_result = game_object.save_object(open_street_objects.objects,
                                                  item_location, 0)
        return save_result

    def save(self,
             object_list=None,
             zone_data=None,
             open_street_data=None,
             store_travel_group_placed_objects=False,
             **kwargs):
        if object_list is None:
            return
        open_street_objects = file_serialization.ObjectList()
        total_beds = 0
        double_bed_exist = False
        kid_bed_exist = False
        alternative_sleeping_spots = 0
        university_roommate_beds = 0
        if store_travel_group_placed_objects:
            objects_to_save_for_clean_up = []
        roommate_bed_tags = set()
        roommate_service = services.get_roommate_service()
        if roommate_service is not None:
            roommate_bed_tags = roommate_service.BED_TAGS
        for game_object in self._objects.values():
            if self._should_save_object_on_lot(game_object):
                save_result = ObjectManager.save_game_object(
                    game_object, object_list, open_street_objects)
                if not save_result:
                    continue
                if zone_data is None:
                    continue
                if store_travel_group_placed_objects and save_result.owner_id != 0:
                    placement_flags = build_buy.get_object_placement_flags(
                        game_object.definition.id)
                    if build_buy.PlacementFlags.NON_INVENTORYABLE not in placement_flags:
                        objects_to_save_for_clean_up.append(save_result)
                if not game_object.definition.has_build_buy_tag(
                        *self._all_bed_tags):
                    continue
                if game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.double_beds):
                    double_bed_exist = True
                    total_beds += 1
                elif game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.kid_beds):
                    total_beds += 1
                    kid_bed_exist = True
                elif game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.other_sleeping_spots):
                    alternative_sleeping_spots += 1
                elif game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.beds):
                    total_beds += 1
                if len(roommate_bed_tags) > 0:
                    if game_object.definition.has_build_buy_tag(
                            *roommate_bed_tags):
                        university_roommate_beds += 1
        if open_street_data is not None:
            open_street_data.objects = open_street_objects
        if zone_data is not None:
            bed_info_data = gameplay_serialization.ZoneBedInfoData()
            bed_info_data.num_beds = total_beds
            bed_info_data.double_bed_exist = double_bed_exist
            bed_info_data.kid_bed_exist = kid_bed_exist
            bed_info_data.alternative_sleeping_spots = alternative_sleeping_spots
            if roommate_service is not None:
                household_and_roommate_cap = roommate_service.HOUSEHOLD_AND_ROOMMATE_CAP
                bed_info_data.university_roommate_beds = min(
                    household_and_roommate_cap, university_roommate_beds)
            zone_data.gameplay_zone_data.bed_info_data = bed_info_data
            if store_travel_group_placed_objects:
                current_zone = services.current_zone()
                save_game_protocol_buffer = services.get_persistence_service(
                ).get_save_game_data_proto()
                self._clear_clean_up_data_for_zone(current_zone,
                                                   save_game_protocol_buffer)
                self._save_clean_up_destination_data(
                    current_zone, objects_to_save_for_clean_up,
                    save_game_protocol_buffer)
        lot = services.current_zone().lot
        for (inventory_type, inventory) in lot.get_all_object_inventories_gen(
                shared_only=True):
            for game_object in inventory:
                game_object.save_object(object_list.objects,
                                        ItemLocation.OBJECT_INVENTORY,
                                        inventory_type)

    def _clear_clean_up_data_for_zone(self, current_zone,
                                      save_game_protocol_buffer):
        current_zone_id = current_zone.id
        current_open_street_id = current_zone.open_street_id
        destination_clean_up_data = save_game_protocol_buffer.destination_clean_up_data
        for clean_up_save_data in destination_clean_up_data:
            indexes_to_clean_up = []
            for (index, old_object_clean_up_data) in enumerate(
                    clean_up_save_data.object_clean_up_data_list):
                if not old_object_clean_up_data.zone_id == current_zone_id:
                    if old_object_clean_up_data.world_id == current_open_street_id:
                        indexes_to_clean_up.append(index)
                indexes_to_clean_up.append(index)
            if len(indexes_to_clean_up) == len(
                    clean_up_save_data.object_clean_up_data_list):
                clean_up_save_data.ClearField('object_clean_up_data_list')
            else:
                for index in reversed(indexes_to_clean_up):
                    del clean_up_save_data.object_clean_up_data_list[index]

    def _save_clean_up_destination_data(self, current_zone,
                                        objects_to_save_for_clean_up,
                                        save_game_protocol_buffer):
        household_manager = services.household_manager()
        travel_group_manager = services.travel_group_manager()
        clean_up_save_data = None
        for object_data in sorted(objects_to_save_for_clean_up,
                                  key=lambda x: x.owner_id):
            owner_id = object_data.owner_id
            if clean_up_save_data is None or clean_up_save_data.household_id != owner_id:
                household = household_manager.get(owner_id)
                travel_group = None
                if household is not None:
                    travel_group = household.get_travel_group()
                for clean_up_save_data in save_game_protocol_buffer.destination_clean_up_data:
                    if clean_up_save_data.household_id != owner_id:
                        continue
                    if travel_group is not None:
                        if travel_group.id == clean_up_save_data.travel_group_id:
                            break
                    if clean_up_save_data.travel_group_id in travel_group_manager:
                        continue
                    break
            with ProtocolBufferRollback(
                    clean_up_save_data.object_clean_up_data_list
            ) as object_clean_up_data:
                if object_data.loc_type == ItemLocation.ON_LOT:
                    object_clean_up_data.zone_id = current_zone.id
                else:
                    object_clean_up_data.world_id = current_zone.open_street_id
                object_clean_up_data.object_data = object_data

    def add_sim_spawn_condition(self, sim_id, callback):
        for sim in services.sim_info_manager().instanced_sims_gen():
            if sim.id == sim_id:
                logger.error(
                    'Sim {} is already in the world, cannot add the spawn condition',
                    sim)
                return
        self._sim_spawn_conditions[sim_id].add(callback)

    def remove_sim_spawn_condition(self, sim_id, callback):
        if callback not in self._sim_spawn_conditions.get(sim_id, ()):
            logger.error(
                'Trying to remove sim spawn condition with invalid id-callback pair ({}-{}).',
                sim_id, callback)
            return
        self._sim_spawn_conditions[sim_id].remove(callback)

    def trigger_sim_spawn_condition(self, sim_id):
        if sim_id in self._sim_spawn_conditions:
            for callback in self._sim_spawn_conditions[sim_id]:
                callback()
            del self._sim_spawn_conditions[sim_id]

    def add_portal_lock(self, sim, callback):
        self.register_portal_added_callback(callback)
        for portal in self.portal_cache_gen():
            portal.lock_sim(sim)

    def register_portal_added_callback(self, callback):
        if callback not in self._portal_added_callbacks:
            self._portal_added_callbacks.append(callback)

    def unregister_portal_added_callback(self, callback):
        if callback in self._portal_added_callbacks:
            self._portal_added_callbacks.remove(callback)

    def register_portal_removed_callback(self, callback):
        if callback not in self._portal_removed_callbacks:
            self._portal_removed_callbacks.append(callback)

    def unregister_portal_removed_callback(self, callback):
        if callback in self._portal_removed_callbacks:
            self._portal_removed_callbacks.remove(callback)

    def _is_valid_portal_object(self, portal):
        portal_component = portal.get_component(PORTAL_COMPONENT)
        if portal_component is None:
            return False
        return portal.has_portals()

    def add_portal_to_cache(self, portal):
        if portal not in self._portal_cache and self._is_valid_portal_object(
                portal):
            self._portal_cache.add(portal)
            self._portal_added_callbacks(portal)

    def remove_portal_from_cache(self, portal):
        if portal in self._portal_cache:
            self._portal_cache.remove(portal)
            self._portal_removed_callbacks(portal)

    def register_front_door_candidates_changed_callback(self, callback):
        if callback not in self._front_door_candidates_changed_callback:
            self._front_door_candidates_changed_callback.append(callback)

    def unregister_front_door_candidates_changed_callback(self, callback):
        if callback in self._front_door_candidates_changed_callback:
            self._front_door_candidates_changed_callback.remove(callback)

    def on_front_door_candidates_changed(self):
        self._front_door_candidates_changed_callback()

    def cleanup_build_buy_transient_objects(self):
        household_inventory_proxy_objects = self.get_objects_matching_tags(
            self.HOUSEHOLD_INVENTORY_OBJECT_TAGS)
        for obj in household_inventory_proxy_objects:
            self.remove(obj)

    def get_objects_matching_tags(self, tags: set, match_any=False):
        matching_objects = None
        for tag in tags:
            objs = self._tag_to_object_list[
                tag] if tag in self._tag_to_object_list else set()
            if matching_objects is None:
                matching_objects = objs
            elif match_any:
                matching_objects |= objs
            else:
                matching_objects &= objs
                if not matching_objects:
                    break
        if matching_objects:
            return frozenset(matching_objects)
        return EMPTY_SET

    def get_num_objects_matching_tags(self, tags: set, match_any=False):
        matching_objects = self.get_objects_matching_tags(tags, match_any)
        return len(matching_objects)

    @contextmanager
    def batch_commodity_flags_update(self):
        default_fn = self.clear_commodity_flags_for_objs_with_affordance
        try:
            affordances = set()
            self.clear_commodity_flags_for_objs_with_affordance = affordances.update
            yield None
        finally:
            self.clear_commodity_flags_for_objs_with_affordance = default_fn
            self.clear_commodity_flags_for_objs_with_affordance(affordances)

    def clear_commodity_flags_for_objs_with_affordance(self, affordances):
        for obj in self.valid_objects():
            if not obj.has_updated_commodity_flags():
                continue
            if any(affordance in affordances
                   for affordance in obj.super_affordances()):
                obj.clear_commodity_flags()

    def get_all_objects_with_component_gen(self, component_definition):
        if component_definition is None:
            return
        for obj in self.valid_objects():
            if obj.has_component(component_definition):
                yield obj

    def get_objects_with_tag_gen(self, tag):
        yield from self.get_objects_matching_tags((tag, ))

    def get_objects_with_tags_gen(self, *tags):
        yield from self.get_objects_matching_tags(tags, match_any=True)

    def on_location_changed(self, obj):
        self._registered_callbacks[CallbackTypes.ON_OBJECT_LOCATION_CHANGED](
            obj)

    def process_invalid_unparented_objects(self):
        invalid_objects = self.get_objects_matching_tags(
            self.INVALID_UNPARENTED_OBJECT_TAGS, match_any=True)
        for invalid_object in invalid_objects:
            if invalid_object.parent is None:
                logger.error(
                    'Invalid unparented object {} existed in game. Cleaning up.',
                    invalid_object)
                invalid_object.destroy(
                    source=invalid_object,
                    cause='Invalid unparented object found on zone spin up.')

    @classproperty
    def supports_parenting(self):
        return True

    def add_active_whim_set(self, whim_set):
        self._whim_set_cache[whim_set] += 1

    def remove_active_whim_set(self, whim_set):
        self._whim_set_cache[whim_set] -= 1
        if self._whim_set_cache[whim_set] <= 0:
            del self._whim_set_cache[whim_set]

    @property
    def active_whim_sets(self):
        return set(self._whim_set_cache.keys())
class CardBattleBehavior(HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'collectable_type':
        TunableEnumEntry(
            description=
            '\n            Id for the card battle collection where the collectible items\n            will be read when a new card needs to be created.\n            ',
            tunable_type=CollectionIdentifier,
            default=CollectionIdentifier.Unindentified,
            invalid_enums=(CollectionIdentifier.Unindentified, )),
        'card_slot_type':
        TunableReference(
            description=
            '\n            Slot type where player card should appear.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SLOT_TYPE)),
        'practice_card':
        TunableReference(
            description=
            '\n            Object reference to use as the default definition as the opponent\n            card.  This is to have the same dummy as the opponent when game is\n            only played by one player.\n            ',
            manager=services.definition_manager()),
        'challenger_buff':
        TunableBuffReference(
            description=
            '\n            The buff to apply to the Sim that started the game.  This is used\n            to be able to guarantee we maintain the challenger Sim consistent\n            since the setup mixers and turns can be run by other Sims\n            depending on route time and other aspects.\n            '
        ),
        'card_information':
        TunableTuple(
            description=
            '\n            Challenger and defender information that will be used to identify\n            specific behavior of the cards depending on their placement.\n            ',
            challenge_state_value=ObjectStateValue.TunableReference(
                description=
                '\n                The state value cards will have when they are selected for \n                a game challenge.\n                '
            ),
            default_state_value=ObjectStateValue.TunableReference(
                description=
                '\n                Default state value of cards after a challenge is done.\n                '
            ),
            level_state=ObjectState.TunableReference(
                description=
                '\n                Level states defining the state values that the card has\n                representing its experience level.\n                '
            ),
            challenger_prop_override=Tunable(
                description=
                '\n                Prop override name for the card placed on the challenger slot.\n                Name for prop should match prop name on swing. \n                ',
                tunable_type=str,
                default=''),
            defender_prop_override=Tunable(
                description=
                '\n                Prop override name for the card placed on the defender slot.\n                Name for prop should match prop name on swing.\n                ',
                tunable_type=str,
                default='')),
        'card_scoring':
        TunableTuple(
            description=
            '\n            Scoring tunables to apply to a card when the game ends.\n            ',
            level_statistic=Statistic.TunableReference(
                description=
                '\n                This statistic is used as the level statistic value to be\n                increased when the card has won a game.\n                '
            ),
            game_won_statistic_increase=TunableRange(
                description=
                '\n                Statistic value to increase if the game is won.\n                Final score increase is affected by the state to stat\n                multiplier.\n                ',
                tunable_type=int,
                default=1,
                minimum=0),
            game_lost_statistic_increase=TunableRange(
                description=
                '\n                Statistic value to increase if the game is lost.\n                Final score increase is affected by the state to stat\n                multiplier.\n                ',
                tunable_type=int,
                default=1,
                minimum=0),
            state_to_stat_multiplier=TunableMapping(
                description=
                "\n                Mapping of card state value to stat multiplier when a game is \n                finished.\n                This value will be multiplied by the \n                game_won_statistic_increase or game_lost_statistic_increase\n                depending if it's a win or a loss.\n                e.g. If card has LEVEL_TWO state value, experience per win is \n                game_won_statistic_increase * multiplier corresponding to the\n                LEVEL_TWO state value.\n                ",
                key_type=ObjectStateValue.TunableReference(
                    description=
                    '\n                    State value the card should have to apply this multiplier\n                    to the statistic increase.\n                    '
                ),
                value_type=TunableRange(
                    description=
                    '\n                    Multiplier that affects the game won statistic increase \n                    on the card.\n                    ',
                    tunable_type=float,
                    default=1,
                    minimum=0))),
        'placement_state_buff':
        TunableList(
            description=
            '\n            List of states and buffs to be applied to the Sim when a card\n            with active state value.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Tuple of state and buff that will be added to the Sim when\n                a card with that specific state value is played.\n                ',
                state_value=ObjectStateValue.TunableReference(
                    description=
                    '\n                    Object state value card needs to have to add the buff\n                    into the Sim.\n                    ',
                    pack_safe=True),
                buff=TunableBuffReference(
                    description=
                    '\n                    The buff to apply to the Sim when a card with this state\n                    is played.\n                    ',
                    pack_safe=True))),
        'card_tag':
        TunableEnumWithFilter(
            description=
            '\n            Tag to look for when iterating through objects to know if they\n            are of the card type.\n            ',
            tunable_type=tag.Tag,
            filter_prefixes=['object', 'func'],
            default=tag.Tag.INVALID,
            invalid_enums=(tag.Tag.INVALID, ))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._players_cards = {}
        self.challenger_definition = None
        self.defender_definition = None
        self._arena_obj = None

    def on_player_added(self, sim, target):
        self._arena_obj = target.part_owner
        candidate_cards = []
        player_card = None
        sim_inventory = sim.inventory_component
        from_inventory = True
        player_slot = self._get_slot_for_sim_position(target, sim.position)
        slotted_objects = player_slot.children
        if slotted_objects:
            player_card = player_slot.children[0]
            if sim.is_npc:
                from_inventory = False
        else:
            for obj in sim_inventory:
                if obj.definition.has_build_buy_tag(self.card_tag):
                    if obj.state_value_active(
                            self.card_information.challenge_state_value):
                        player_card = obj
                        player_card.set_state(
                            self.card_information.default_state_value.state,
                            self.card_information.default_state_value)
                        break
                    candidate_cards.append(obj)
            if player_card is None:
                if candidate_cards:
                    player_card = random.choice(candidate_cards)
                else:
                    from_inventory = False
                    card_options = ObjectCollectionData.get_collection_data(
                        self.collectable_type).object_list
                    if not card_options:
                        logger.error('Collection {} is an invalid id',
                                     self.collectable_type)
                        return
                    card_definition = random.choice(
                        card_options).collectable_item
                    player_card = create_object(card_definition)
                    card_level_state_value = random.choice(
                        self.card_information.level_state.values)
                    player_card.set_state(card_level_state_value.state,
                                          card_level_state_value)
                    player_card.persistence_group = PersistenceGroups.NONE
        if player_card is None:
            logger.error(
                'Failed to create card for player {} for card candidates {}',
                sim, candidate_cards)
        card_definition = player_card.get_game_animation_definition()
        if card_definition is None:
            logger.error(
                'Card {} has no game animation definition tuned and will not be displayed on the card battle object',
                player_card)
            return
        if self.challenger_definition is None:
            self.challenger_definition = card_definition
            sim.add_buff_from_op(buff_type=self.challenger_buff.buff_type,
                                 buff_reason=self.challenger_buff.buff_reason)
        else:
            self.defender_definition = card_definition
        self._create_card_on_slot(player_card, player_slot)
        self._apply_card_placement_bonus(sim, player_card)
        reservation_handler = player_card.get_reservation_handler(sim)
        reservation_handler.begin_reservation()
        self._players_cards[sim] = (player_card, from_inventory,
                                    reservation_handler)

    def on_setup_game(self, game_object):
        pass

    def on_game_ended(self, winning_team, game_object):
        for sim in list(self._players_cards):
            if winning_team is not None:
                if sim in winning_team.players:
                    self._update_card_scoring(
                        sim, self.card_scoring.game_won_statistic_increase)
                else:
                    self._update_card_scoring(
                        sim, self.card_scoring.game_lost_statistic_increase)
            self.on_player_removed(sim, from_game_ended=True)
        self.challenger_definition = None
        self.defender_definition = None
        self._arena_obj = None

    def _update_card_scoring(self, sim, win_loss_score):
        (card, from_inventory, _) = self._players_cards[sim]
        if card is None:
            logger.error(
                'Game ended but Sim {} was removed earlier, this will cause cards to not be updated',
                sim)
            return
        if not from_inventory:
            return
        level_state_value = card.get_state(self.card_information.level_state)
        if level_state_value is None:
            logger.error(
                "Card {} doesn't support the state {} used for card scoring",
                card, self.card_information.level_state)
            return
        score_multiplier = self.card_scoring.state_to_stat_multiplier.get(
            level_state_value)
        if score_multiplier is None:
            logger.error(
                'Card scoring tuning error, state value {} is not tuned inside the multiplier range of the game',
                level_state_value)
            return
        level_statistic = card.get_stat_instance(
            self.card_scoring.level_statistic, add=True)
        if level_statistic is not None:
            level_statistic.tracker.add_value(
                self.card_scoring.level_statistic,
                win_loss_score * score_multiplier)

    def _apply_card_placement_bonus(self, sim, card):
        for placement_modifier in self.placement_state_buff:
            if card.state_value_active(placement_modifier.state_value):
                sim.add_buff_from_op(
                    buff_type=placement_modifier.buff.buff_type,
                    buff_reason=placement_modifier.buff.buff_reason)

    def on_player_removed(self, sim, from_game_ended=False):
        if sim not in self._players_cards:
            return
        if not from_game_ended:
            self._update_card_scoring(
                sim, self.card_scoring.game_lost_statistic_increase)
        (card, from_inventory, reservation_handler) = self._players_cards[sim]
        reservation_handler.end_reservation()
        if from_inventory:
            sim.inventory_component.player_try_add_object(card)
        else:
            card.set_parent(None)
            card.destroy(source=self,
                         cause='GameComponent: Placeholder game card removed.')
        del self._players_cards[sim]
        if sim.has_buff(self.challenger_buff.buff_type):
            sim.remove_buff_by_type(self.challenger_buff.buff_type)

    def _create_card_on_slot(self, card, slot):
        if slot is not None and slot.empty:
            slot.add_child(card)

    def _get_slot_for_sim_position(self, target, sim_position):
        max_magnitude = None
        closest_slot = None
        for runtime_slot in target.part_owner.get_runtime_slots_gen(
                slot_types={self.card_slot_type}):
            difference_vector = runtime_slot.position - sim_position
            difference_magnitude = difference_vector.magnitude()
            if not max_magnitude is None:
                if difference_magnitude < max_magnitude:
                    closest_slot = runtime_slot
                    max_magnitude = difference_magnitude
            closest_slot = runtime_slot
            max_magnitude = difference_magnitude
        return closest_slot

    def additional_anim_overrides_gen(self):
        prop_overrides = {}
        if self.challenger_definition is not None:
            self._set_prop_override(
                prop_overrides, self.card_information.challenger_prop_override,
                self.challenger_definition)
            if self.defender_definition is None:
                self._set_prop_override(
                    prop_overrides,
                    self.card_information.defender_prop_override,
                    self.practice_card)
        if self.defender_definition is not None:
            self._set_prop_override(
                prop_overrides, self.card_information.defender_prop_override,
                self.defender_definition)
            if self.challenger_definition is None:
                self._set_prop_override(
                    prop_overrides,
                    self.card_information.challenger_prop_override,
                    self.practice_card)
        yield AnimationOverrides(props=prop_overrides)

    def _set_prop_override(self, prop_overrides, override_name,
                           card_definition):
        prop_overrides[override_name] = sims4.collections.FrozenAttributeDict({
            'states_to_override': (),
            'from_actor':
            None,
            'definition':
            card_definition,
            'sharing':
            None,
            'set_as_actor':
            None
        })
예제 #22
0
class SituationManager(DistributableObjectManager):
    __qualname__ = 'SituationManager'
    DEFAULT_LEAVE_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The situation type for the background leave situation.\n                                            It collects sims who are not in other situations and\n                                            asks them to leave periodically.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=situations.complex.leave_situation.LeaveSituation)
    DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The situation type that drives the sim off the lot pronto.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=situations.complex.single_sim_leave_situation.
        SingleSimLeaveSituation)
    DEFAULT_VISIT_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The default visit situation used when you ask someone to \n                                            hang out or invite them in.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION))
    DEFAULT_TRAVEL_SITUATION = Situation.TunableReference(
        description=
        ' \n                                            The default situation for when you \n                                            are simply traveling with a group \n                                            of Sims.\n                                            '
    )
    NPC_SOFT_CAP = sims4.tuning.tunable.Tunable(
        description=
        '\n                The base value for calculating the soft cap on the maximum \n                number of NPCs instantiated.\n                \n                The actual value of the NPC soft cap will be\n                this tuning value minus the number of sims in the active household.\n                \n                There is no hard cap because certain types of NPCs must always\n                spawn or the game will be broken. The prime example of a \n                game breaker is the Grim Reaper.\n                \n                If the number of NPCs is:\n                \n                1) At or above the soft cap only game breaker NPCs will be spawned.\n                \n                2) Above the soft cap then low priority NPCs will be driven from the lot.\n                \n                3) Equal to the soft cap and there are pending requests for higher priority\n                NPCs, then lower priority NPCs will be driven from the lot.\n                                \n                ',
        tunable_type=int,
        default=20,
        tuning_filter=sims4.tuning.tunable_base.FilterTag.EXPERT_MODE)
    LEAVE_INTERACTION_TAGS = TunableSet(
        description=
        '\n                The tags indicating leave lot interactions, but not \n                leave lot must run interactions.\n                These are used to determine if a leave lot interaction is running\n                or cancel one if it is.\n                ',
        tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                      default=tag.Tag.INVALID,
                                      tuning_filter=FilterTag.EXPERT_MODE,
                                      filter_prefixes=tag.INTERACTION_PREFIX))
    SUPER_SPEED_THREE_REQUEST_BUFF = TunableBuffReference(
        description=
        "\n        The buff to apply to the Sim when we're trying to make them run the\n        leave situation from a super speed three request.\n        ",
        deferred=True)
    _npc_soft_cap_override = None
    _perf_test_cheat_enabled = False

    def __init__(self, manager_id=0):
        super().__init__(manager_id=manager_id)
        self._get_next_session_id = UniqueIdGenerator(1)
        self._added_to_distributor = set()
        self._callbacks = {}
        self._departing_situation_seed = None
        self._arriving_situation_seed = None
        self._zone_seeds_for_zone_spinup = []
        self._open_street_seeds_for_zone_spinup = []
        self._debug_sims = set()
        self._leave_situation_id = 0
        self._player_greeted_situation_id = 0
        self._player_waiting_to_be_greeted_situation_id = 0
        self._sim_being_created = None
        self._sim_data = {}
        self._delay_situation_destruction_ref_count = 0
        self._situations_for_delayed_destruction = set()
        self._bouncer = None
        self._zone_spin_up_greeted_complete = False
        self._pre_bouncer_update = []

    def start(self):
        self._bouncer = Bouncer()

    def destroy_situations_on_teardown(self):
        self.destroy_all_situations(include_system=True)
        self._sim_data.clear()
        self._bouncer.destroy()
        self._bouncer = None

    def reset(self, create_system_situations=True):
        self.destroy_all_situations(include_system=True)
        self._added_to_distributor.clear()
        self._callbacks.clear()
        self._bouncer.reset()
        if create_system_situations:
            self._create_system_situations()

    def add_pre_bouncer_update(self, situation):
        self._pre_bouncer_update.append(situation)

    def update(self):
        if self._bouncer is not None:
            try:
                situations = tuple(self._pre_bouncer_update)
                if situations:
                    self._pre_bouncer_update = []
                    for situation in situations:
                        situation.on_pre_bouncer_update()
                self._bouncer._update()
            except Exception:
                logger.exception('Exception while updating the Bouncer.')

    @property
    def npc_soft_cap(self):
        cap = self.NPC_SOFT_CAP if self._npc_soft_cap_override is None else self._npc_soft_cap_override
        if services.active_household() is None:
            return 0
        return cap - services.active_household().size_of_household

    def set_npc_soft_cap_override(self, override):
        self._npc_soft_cap_override = override

    def enable_perf_cheat(self, enable=True):
        self._perf_test_cheat_enabled = enable
        self._bouncer.spawning_freeze(enable)
        self._bouncer.cap_cheat(enable)

    def get_all(self):
        return [
            obj for obj in self._objects.values()
            if obj._stage == SituationStage.RUNNING
        ]

    def get_new_situation_creation_session(self):
        return self._get_next_session_id()

    @property
    def bouncer(self):
        return self._bouncer

    @property
    def sim_being_created(self):
        return self._sim_being_created

    def add_debug_sim_id(self, sim_id):
        self._debug_sims.add(sim_id)

    def _determine_player_greeted_status_during_zone_spin_up(self):
        if not services.current_zone(
        ).venue_service.venue.requires_visitation_rights:
            return GreetedStatus.NOT_APPLICABLE
        active_household = services.active_household()
        if active_household is None:
            return GreetedStatus.NOT_APPLICABLE
        if active_household.home_zone_id == services.current_zone().id:
            return GreetedStatus.NOT_APPLICABLE
        cur_status = GreetedStatus.WAITING_TO_BE_GREETED
        lot_seeds = list(self._zone_seeds_for_zone_spinup)
        if self._arriving_situation_seed is not None:
            lot_seeds.append(self._arriving_situation_seed)
        for seed in lot_seeds:
            status = seed.get_player_greeted_status()
            logger.debug('Player:{} :{}',
                         status,
                         seed.situation_type,
                         owner='sscholl')
            while status == GreetedStatus.GREETED:
                cur_status = status
                break
        return cur_status

    def get_npc_greeted_status_during_zone_fixup(self, sim_info):
        if not services.current_zone(
        ).venue_service.venue.requires_visitation_rights:
            return GreetedStatus.NOT_APPLICABLE
        if sim_info.lives_here:
            return GreetedStatus.NOT_APPLICABLE
        cur_status = GreetedStatus.NOT_APPLICABLE
        for seed in self._zone_seeds_for_zone_spinup:
            status = seed.get_npc_greeted_status(sim_info)
            logger.debug('NPC:{} :{} :{}',
                         sim_info,
                         status,
                         seed.situation_type,
                         owner='sscholl')
            if status == GreetedStatus.GREETED:
                cur_status = status
                break
            while status == GreetedStatus.WAITING_TO_BE_GREETED:
                cur_status = status
        return cur_status

    def is_player_greeted(self):
        return self._player_greeted_situation_id != 0

    def is_player_waiting_to_be_greeted(self):
        return self._player_waiting_to_be_greeted_situation_id != 0 and self._player_greeted_situation_id == 0

    @property
    def is_zone_spin_up_greeted_complete(self):
        return self._zone_spin_up_greeted_complete

    def create_situation(self,
                         situation_type,
                         guest_list=None,
                         user_facing=True,
                         duration_override=None,
                         custom_init_writer=None,
                         zone_id=0,
                         scoring_enabled=True,
                         spawn_sims_during_zone_spin_up=False):
        if guest_list is None:
            guest_list = SituationGuestList()
        hire_cost = guest_list.get_hire_cost()
        reserved_funds = None
        if guest_list.host_sim is not None:
            reserved_funds = guest_list.host_sim.family_funds.try_remove(
                situation_type.cost() + hire_cost,
                Consts_pb2.TELEMETRY_EVENT_COST, guest_list.host_sim)
            if reserved_funds is None:
                return
            reserved_funds.apply()
        situation_id = id_generator.generate_object_id()
        self._send_create_situation_telemetry(situation_type, situation_id,
                                              guest_list, hire_cost, zone_id)
        if zone_id != 0 and services.current_zone().id != zone_id:
            return self._create_departing_seed_and_travel(
                situation_type,
                situation_id,
                guest_list,
                user_facing,
                duration_override,
                custom_init_writer,
                zone_id,
                scoring_enabled=scoring_enabled)
        situation_seed = SituationSeed(
            situation_type,
            SeedPurpose.NORMAL,
            situation_id,
            guest_list,
            user_facing=user_facing,
            duration_override=duration_override,
            scoring_enabled=scoring_enabled,
            spawn_sims_during_zone_spin_up=spawn_sims_during_zone_spin_up)
        if custom_init_writer is not None:
            situation_seed.setup_for_custom_init_params(custom_init_writer)
        return self.create_situation_from_seed(situation_seed)

    def create_visit_situation_for_unexpected(self, sim):
        duration_override = None
        if self._perf_test_cheat_enabled:
            duration_override = 0
        self.create_visit_situation(sim, duration_override=duration_override)

    def create_visit_situation(self,
                               sim,
                               duration_override=None,
                               visit_type_override=None):
        situation_id = None
        visit_type = visit_type_override if visit_type_override is not None else self.DEFAULT_VISIT_SITUATION
        if visit_type is not None:
            guest_list = situations.situation_guest_list.SituationGuestList(
                invite_only=True)
            guest_info = situations.situation_guest_list.SituationGuestInfo.construct_from_purpose(
                sim.id, visit_type.default_job(), situations.
                situation_guest_list.SituationInvitationPurpose.INVITED)
            guest_list.add_guest_info(guest_info)
            situation_id = self.create_situation(
                visit_type,
                guest_list=guest_list,
                user_facing=False,
                duration_override=duration_override)
        if situation_id is None:
            logger.error('Failed to create visit situation for sim: {}', sim)
            self.make_sim_leave(sim)
        return situation_id

    def create_situation_from_seed(self, seed):
        if not seed.allow_creation:
            return
        if seed.user_facing:
            situation = self.get_user_facing_situation()
            if situation is not None:
                self.destroy_situation_by_id(situation.id)
        if seed.situation_type.is_unique_situation:
            for situation in self.running_situations():
                while type(situation) is seed.situation_type:
                    return
        situation = seed.situation_type(seed)
        try:
            if seed.is_loadable:
                situation._destroy()
                return
            else:
                situation.start_situation()
        except Exception:
            logger.exception('Exception thrown while starting situation')
            situation._destroy()
            return
        self.add(situation)
        if situation.is_user_facing or situation.distribution_override:
            distributor.system.Distributor.instance().add_object(situation)
            self._added_to_distributor.add(situation)
            situation.on_added_to_distributor()
        return situation.id

    def _create_departing_seed_and_travel(self,
                                          situation_type,
                                          situation_id,
                                          guest_list=None,
                                          user_facing=True,
                                          duration_override=None,
                                          custom_init_writer=None,
                                          zone_id=0,
                                          scoring_enabled=True):
        traveling_sim = guest_list.get_traveler()
        if traveling_sim is None:
            logger.error(
                'No traveling sim available for creating departing seed for situation: {}.',
                situation_type)
            return
        if traveling_sim.client is None:
            logger.error(
                'No client on traveling sim: {} for for situation: {}.',
                traveling_sim, situation_type)
            return
        if traveling_sim.household is None:
            logger.error(
                'No household on traveling sim for for situation: {}.',
                situation_type)
            return
        situation_seed = SituationSeed(situation_type,
                                       SeedPurpose.TRAVEL,
                                       situation_id,
                                       guest_list,
                                       user_facing,
                                       duration_override,
                                       zone_id,
                                       scoring_enabled=scoring_enabled)
        if situation_seed is None:
            logger.error('Failed to create departing seed.for situation: {}.',
                         situation_type)
            return
        if custom_init_writer is not None:
            situation_seed.setup_for_custom_init_params(custom_init_writer)
        self._departing_situation_seed = situation_seed
        travel_info = protocolbuffers.InteractionOps_pb2.TravelSimsToZone()
        travel_info.zone_id = zone_id
        travel_info.sim_ids.append(traveling_sim.id)
        traveling_sim_ids = guest_list.get_other_travelers(traveling_sim)
        travel_info.sim_ids.extend(traveling_sim_ids)
        distributor.system.Distributor.instance().add_event(
            protocolbuffers.Consts_pb2.MSG_TRAVEL_SIMS_TO_ZONE, travel_info)
        services.game_clock_service().request_pause('Situation Travel')
        logger.debug('Travel seed creation time {}',
                     services.time_service().sim_now)
        logger.debug('Travel seed future time {}',
                     services.time_service().sim_future)
        return situation_id

    def _create_system_situations(self):
        self._leave_situation_id = 0
        for situation in self.running_situations():
            while type(situation) is self.DEFAULT_LEAVE_SITUATION:
                self._leave_situation_id = situation.id
                break
        if self._leave_situation_id == 0:
            self._leave_situation_id = self.create_situation(
                self.DEFAULT_LEAVE_SITUATION,
                user_facing=False,
                duration_override=0)

    @property
    def auto_manage_distributor(self):
        return False

    def call_on_remove(self, situation):
        super().call_on_remove(situation)
        self._callbacks.pop(situation.id, None)
        if situation in self._added_to_distributor:
            dist = distributor.system.Distributor.instance()
            dist.remove_object(situation)
            self._added_to_distributor.remove(situation)
            situation.on_removed_from_distributor()

    def is_distributed(self, situation):
        return situation in self._added_to_distributor

    def _request_destruction(self, situation):
        if self._delay_situation_destruction_ref_count == 0:
            return True
        self._situations_for_delayed_destruction.add(situation)
        return False

    def destroy_situation_by_id(self, situation_id):
        if situation_id in self:
            if situation_id == self._leave_situation_id:
                self._leave_situation_id = 0
            if situation_id == self._player_greeted_situation_id:
                self._player_greeted_situation_id = 0
            if situation_id == self._player_waiting_to_be_greeted_situation_id:
                self._player_waiting_to_be_greeted_situation_id = 0
            self.remove_id(situation_id)

    def destroy_all_situations(self, include_system=False):
        all_situations = tuple(self.values())
        for situation in all_situations:
            if include_system == False and situation.id == self._leave_situation_id:
                pass
            try:
                self.destroy_situation_by_id(situation.id)
            except Exception:
                logger.error(
                    'Error when destroying situation {}. You are probably screwed.',
                    situation)

    def register_for_callback(self, situation_id, situation_callback_option,
                              callback_fn):
        registrant = _CallbackRegistration(situation_callback_option,
                                           callback_fn)
        registrant_list = self._callbacks.setdefault(situation_id, [])
        registrant_list.append(registrant)

    def create_greeted_npc_visiting_npc_situation(self, npc_sim_info):
        services.current_zone().venue_service.venue.summon_npcs(
            (npc_sim_info, ),
            venues.venue_constants.NPCSummoningPurpose.PLAYER_BECOMES_GREETED)

    def create_greeted_player_visiting_npc_situation(self, sim=None):
        if sim is None:
            guest_list = situations.situation_guest_list.SituationGuestList()
        else:
            guest_list = situations.situation_guest_list.SituationGuestList(
                host_sim_id=sim.id)
        greeted_situation_type = services.current_zone(
        ).venue_service.venue.player_greeted_situation_type
        if greeted_situation_type is None:
            return
        self._player_greeted_situation_id = self.create_situation(
            greeted_situation_type, user_facing=False, guest_list=guest_list)

    def create_player_waiting_to_be_greeted_situation(self):
        self._player_waiting_to_be_greeted_situation_id = self.create_situation(
            services.current_zone(
            ).venue_service.venue.player_ungreeted_situation_type,
            user_facing=False)

    def _handle_player_greeting_situations_during_zone_spin_up(self):
        if self._zone_spin_up_player_greeted_status == GreetedStatus.NOT_APPLICABLE:
            return
        if self._zone_spin_up_player_greeted_status == GreetedStatus.GREETED:
            greeted_situation_type = services.current_zone(
            ).venue_service.venue.player_greeted_situation_type
            for situation in self.running_situations():
                while type(situation) is greeted_situation_type:
                    break
            self.create_greeted_player_visiting_npc_situation()
            return
        waiting_situation_type = services.current_zone(
        ).venue_service.venue.player_ungreeted_situation_type
        for situation in self.running_situations():
            while type(situation) is waiting_situation_type:
                break
        self.create_player_waiting_to_be_greeted_situation()

    def handle_npcs_during_zone_fixup(self):
        if services.game_clock_service(
        ).time_has_passed_in_world_since_zone_save() or services.current_zone(
        ).active_household_changed_between_save_and_load():
            sim_infos_to_fix_up = []
            for sim_info in services.sim_info_manager(
            ).get_sim_infos_saved_in_zone():
                while sim_info.is_npc and not sim_info.lives_here and sim_info.get_sim_instance(
                ) is not None:
                    sim_infos_to_fix_up.append(sim_info)
            if sim_infos_to_fix_up:
                logger.debug('Fixing up {} npcs during zone fixup',
                             len(sim_infos_to_fix_up),
                             owner='sscholl')
                services.current_zone().venue_service.venue.zone_fixup(
                    sim_infos_to_fix_up)

    def make_waiting_player_greeted(self, door_bell_ringing_sim=None):
        for situation in self.running_situations():
            situation._on_make_waiting_player_greeted(door_bell_ringing_sim)
        if self._player_greeted_situation_id == 0:
            self.create_greeted_player_visiting_npc_situation(
                door_bell_ringing_sim)

    def save(self,
             zone_data=None,
             open_street_data=None,
             save_slot_data=None,
             **kwargs):
        if zone_data is None:
            return
        SituationSeed.serialize_travel_seed_to_slot(
            save_slot_data, self._departing_situation_seed)
        zone_seeds = []
        street_seeds = []
        for situation in self.running_situations():
            seed = situation.save_situation()
            while seed is not None:
                if situation.situation_serialization_option == SituationSerializationOption.OPEN_STREETS:
                    street_seeds.append(seed)
                else:
                    zone_seeds.append(seed)
        SituationSeed.serialize_seeds_to_zone(zone_seeds, zone_data)
        SituationSeed.serialize_seeds_to_open_street(street_seeds,
                                                     open_street_data)

    def on_pre_spawning_sims(self):
        zone = services.current_zone()
        save_slot_proto = services.get_persistence_service(
        ).get_save_slot_proto_buff()
        seed = SituationSeed.deserialize_travel_seed_from_slot(save_slot_proto)
        if seed is not None:
            if zone.id != seed.zone_id:
                logger.debug(
                    'Travel situation :{} not loaded. Expected zone :{} but is on zone:{}',
                    seed.situation_type, seed.zone_id,
                    services.current_zone().id)
                seed.allow_creation = False
            else:
                time_since_travel_seed_created = services.time_service(
                ).sim_now - seed.create_time
                if time_since_travel_seed_created > date_and_time.TimeSpan.ZERO:
                    logger.debug(
                        'Not loading traveled situation :{} because time has passed {}',
                        seed.situation_type, time_since_travel_seed_created)
                    seed.allow_creation = False
        self._arriving_situation_seed = seed
        zone_proto = services.get_persistence_service().get_zone_proto_buff(
            zone.id)
        if zone_proto is not None:
            self._zone_seeds_for_zone_spinup = SituationSeed.deserialize_seeds_from_zone(
                zone_proto)
        for seed in self._zone_seeds_for_zone_spinup:
            while not seed.situation_type._should_seed_be_loaded(seed):
                seed.allow_creation = False
        open_street_proto = services.get_persistence_service(
        ).get_open_street_proto_buff(zone.open_street_id)
        if open_street_proto is not None:
            self._open_street_seeds_for_zone_spinup = SituationSeed.deserialize_seeds_from_open_street(
                open_street_proto)
        for seed in self._open_street_seeds_for_zone_spinup:
            while not seed.situation_type._should_seed_be_loaded(seed):
                seed.allow_creation = False
        self._zone_spin_up_player_greeted_status = self._determine_player_greeted_status_during_zone_spin_up(
        )

    def create_situations_during_zone_spin_up(self):
        for seed in self._zone_seeds_for_zone_spinup:
            self.create_situation_from_seed(seed)
        for seed in self._open_street_seeds_for_zone_spinup:
            self.create_situation_from_seed(seed)
        self._create_system_situations()
        if self._arriving_situation_seed is not None:
            self.create_situation_from_seed(self._arriving_situation_seed)
        self._handle_player_greeting_situations_during_zone_spin_up()
        self.handle_npcs_during_zone_fixup()

    def on_all_situations_created_during_zone_spin_up(self):
        self._bouncer.start()

    def get_sim_serialization_option(self, sim):
        result = sims.sim_info_types.SimSerializationOption.UNDECLARED
        for situation in self.get_situations_sim_is_in(sim):
            option = situation.situation_serialization_option
            if option == situations.situation_types.SituationSerializationOption.LOT:
                result = sims.sim_info_types.SimSerializationOption.LOT
                break
            else:
                while option == situations.situation_types.SituationSerializationOption.OPEN_STREETS:
                    result = sims.sim_info_types.SimSerializationOption.OPEN_STREETS
        return result

    def remove_sim_from_situation(self, sim, situation_id):
        situation = self.get(situation_id)
        if situation is None:
            return
        self._bouncer.remove_sim_from_situation(sim, situation)

    def on_reset(self, sim_ref):
        pass

    def on_sim_creation(self, sim):
        sim_data = self._sim_data.setdefault(sim.id,
                                             _SituationManagerSimData(sim.id))
        sim_data.set_created_time(services.time_service().sim_now)
        self._prune_sim_data()
        self._sim_being_created = sim
        if sim.id in self._debug_sims:
            self._debug_sims.discard(sim.id)
            if self._perf_test_cheat_enabled:
                self.create_visit_situation_for_unexpected(sim)
            else:
                services.current_zone().venue_service.venue.summon_npcs(
                    (sim.sim_info, ), NPCSummoningPurpose.DEFAULT)
        self._bouncer.on_sim_creation(sim)
        self._sim_being_created = None

    def get_situations_sim_is_in(self, sim):
        return [
            situation for situation in self.values()
            if situation._stage == SituationStage.RUNNING
        ]

    def is_user_facing_situation_running(self):
        for situation in self.values():
            while situation.is_user_facing:
                return True
        return False

    def get_user_facing_situation(self):
        for situation in self.values():
            while situation.is_user_facing:
                return situation

    def running_situations(self):
        return [
            obj for obj in self._objects.values()
            if obj._stage == SituationStage.RUNNING
        ]

    def is_situation_with_tags_running(self, tags):
        for situation in self.values():
            while situation._stage == SituationStage.RUNNING and situation.tags & tags:
                return True
        return False

    def user_ask_sim_to_leave_now_must_run(self, sim):
        if not sim.sim_info.is_npc or sim.sim_info.lives_here:
            return
        ask_to_leave = True
        for situation in self.get_situations_sim_is_in(sim):
            while not situation.on_ask_sim_to_leave(sim):
                ask_to_leave = False
                break
        if ask_to_leave:
            self.make_sim_leave_now_must_run(sim)

    def make_sim_leave_now_must_run(self,
                                    sim,
                                    super_speed_three_request=False):
        if services.current_zone().is_zone_shutting_down:
            return
        for situation in self.get_situations_sim_is_in(sim):
            while type(situation) is self.DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION:
                return
        if super_speed_three_request:
            sim.add_buff(
                buff_type=self.SUPER_SPEED_THREE_REQUEST_BUFF.buff_type,
                buff_reason=self.SUPER_SPEED_THREE_REQUEST_BUFF.buff_reason)
        leave_now_type = self.DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION
        guest_list = situations.situation_guest_list.SituationGuestList(
            invite_only=True)
        guest_info = situations.situation_guest_list.SituationGuestInfo(
            sim.id,
            leave_now_type.default_job(),
            RequestSpawningOption.CANNOT_SPAWN,
            BouncerRequestPriority.VIP,
            expectation_preference=True)
        guest_list.add_guest_info(guest_info)
        self.create_situation(leave_now_type,
                              guest_list=guest_list,
                              user_facing=False)

    def make_sim_leave(self, sim):
        leave_situation = self.get(self._leave_situation_id)
        if leave_situation is None:
            logger.error(
                'The leave situation is missing. Making the sim leave now must run.'
            )
            self.make_sim_leave_now_must_run(sim)
            return
        leave_situation.invite_sim_to_leave(sim)

    def expedite_leaving(self):
        leave_situation = self.get(self._leave_situation_id)
        if leave_situation is None:
            return
        for sim in leave_situation.all_sims_in_situation_gen():
            self.make_sim_leave_now_must_run(sim)

    def get_time_span_sim_has_been_on_lot(self, sim):
        sim_data = self._sim_data.get(sim.id)
        if sim_data is None:
            return
        if sim_data.created_time is None:
            return
        return services.time_service().sim_now - sim_data.created_time

    def get_remaining_blacklist_time_span(self, sim_id):
        sim_data = self._sim_data.get(sim_id)
        if sim_data is None:
            return date_and_time.TimeSpan.ZERO
        return sim_data.get_remaining_blacklisted_time_span()

    def get_auto_fill_blacklist(self):
        blacklist = set()
        for (sim_id, sim_data) in tuple(self._sim_data.items()):
            while sim_data.is_blacklisted:
                blacklist.add(sim_id)
        return blacklist

    def add_sim_to_auto_fill_blacklist(self, sim_id, blacklist_until=None):
        sim_data = self._sim_data.setdefault(sim_id,
                                             _SituationManagerSimData(sim_id))
        sim_data.blacklist(blacklist_until=blacklist_until)
        self._prune_sim_data()

    def _prune_sim_data(self):
        to_remove_ids = []
        for (sim_id, sim_data) in self._sim_data.items():
            while services.object_manager().get(
                    sim_id) is None and sim_data.is_blacklisted == False:
                to_remove_ids.append(sim_id)
        for sim_id in to_remove_ids:
            del self._sim_data[sim_id]

    def _get_callback_registrants(self, situation_id):
        return list(self._callbacks.get(situation_id, []))

    def _send_create_situation_telemetry(self, situation_type, situation_id,
                                         guest_list, hire_cost, zone_id):
        if hasattr(situation_type, 'guid64'):
            with telemetry_helper.begin_hook(
                    writer, TELEMETRY_HOOK_CREATE_SITUATION) as hook:
                hook.write_int('situ', situation_id)
                hook.write_int('host', guest_list.host_sim_id)
                hook.write_guid('type', situation_type.guid64)
                hook.write_bool('invi', guest_list.invite_only)
                hook.write_bool('hire', hire_cost)
                hook.write_bool(
                    'nzon', zone_id != 0
                    and services.current_zone().id != zone_id)
            sim_info_manager = services.sim_info_manager()
            if sim_info_manager is not None:
                while True:
                    for guest_infos in guest_list._job_type_to_guest_infos.values(
                    ):
                        for guest_info in guest_infos:
                            if guest_info.sim_id == 0:
                                pass
                            guest_sim = sim_info_manager.get(guest_info.sim_id)
                            if guest_sim is None:
                                pass
                            client = services.client_manager(
                            ).get_client_by_household_id(
                                guest_sim.household_id)
                            with telemetry_helper.begin_hook(
                                    writer, TELEMETRY_HOOK_GUEST) as hook:
                                hook.write_int('situ', situation_id)
                                if client is None:
                                    hook.write_int('npcg', guest_info.sim_id)
                                else:
                                    hook.write_int('pcgu', guest_info.sim_id)
                                    hook.write_guid('jobb',
                                                    guest_info.job_type.guid64)
예제 #23
0
class RestaurantTuning:
    MENU_PRESETS = TunableMapping(
        description=
        '\n        The map to tune preset of menus that player to select to use in\n        restaurant customization.\n        ',
        key_type=TunableEnumEntry(tunable_type=MenuPresets,
                                  default=MenuPresets.CUSTOMIZE,
                                  binary_type=EnumBinaryExportType.EnumUint32),
        value_type=TunableTuple(
            description='\n            Menu preset contents.\n            ',
            preset_name=TunableLocalizedString(
                description=
                '\n                Menu preset name that appear in both menu customize UI and in\n                game menu UI.\n                '
            ),
            recipe_map=TunableMapping(
                description=
                "\n                The map that represent a menu preset. It's organized with courses\n                like drink, appetizer, entree etc, and in each course there are\n                options of recipes.\n                ",
                key_type=TunableEnumWithFilter(
                    tunable_type=Tag,
                    filter_prefixes=['recipe_course'],
                    default=Tag.INVALID,
                    invalid_enums=(Tag.INVALID, ),
                    pack_safe=True,
                    binary_type=EnumBinaryExportType.EnumUint32),
                value_type=TunableSet(
                    tunable=TunableReference(manager=services.recipe_manager(),
                                             class_restrictions=('Recipe', ),
                                             pack_safe=True)),
                key_name='course_tags',
                value_name='recipes',
                tuple_name='MenuCourseMappingTuple'),
            show_in_restaurant_menu=Tunable(
                description=
                "\n                If this is enabled, this menu preset will show up on restaurant\n                menus. If not, it won't. Currently, only home-chef menus\n                shouldn't show up on restaurant menus.\n                ",
                tunable_type=bool,
                default=True),
            export_class_name='MenuPresetContentTuple'),
        key_name='preset_enum',
        value_name='preset_contents',
        tuple_name='MenuPresetMappingTuple',
        export_modes=ExportModes.All)
    MENU_TAG_DISPLAY_CONTENTS = TunableMapping(
        description=
        '\n        The map to tune menu tags to display contents.\n        ',
        key_type=TunableEnumWithFilter(
            tunable_type=Tag,
            filter_prefixes=['recipe'],
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True,
            binary_type=EnumBinaryExportType.EnumUint32),
        value_type=TunableTuple(
            description=
            '\n            menu tag display contents.\n            ',
            menu_tag_name=TunableLocalizedString(),
            menu_tag_icon=TunableResourceKey(
                description=
                '\n                This will display as the filter icon in the course recipe picker UI.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            export_class_name='MenuTagDisplayTuple'),
        key_name='menu_tags',
        value_name='menu_tag_display_contents',
        tuple_name='MenuTagDisplayMappingTuple',
        export_modes=ExportModes.ClientBinary)
    COURSE_SORTING_SEQUENCE = TunableSet(
        description=
        '\n        This set determines the sorting sequence for courses in both menu\n        customize UI and in game menu UI.\n        ',
        tunable=TunableEnumWithFilter(
            tunable_type=Tag,
            filter_prefixes=['recipe_course'],
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True,
            binary_type=EnumBinaryExportType.EnumUint32),
        export_modes=ExportModes.ClientBinary)
    DAILY_SPECIAL_DISCOUNT = TunablePercent(
        description=
        '\n        The percentage of the base price when an item is the daily special.\n        For example, if the base price is $10 and this is tuned to 80%, the\n        discounted price will be $10 x 80% = $8\n        ',
        default=80)
    INVALID_DAILY_SPECIAL_RECIPES = TunableList(
        description=
        '\n        A list of recipes that should not be considered for daily specials.\n        i.e. Glass of water.\n        ',
        tunable=TunableReference(
            description=
            '\n            The recipe to disallow from being a daily special.\n            ',
            manager=services.recipe_manager(),
            class_restrictions=('Recipe', ),
            pack_safe=True))
    COURSE_TO_FILTER_TAGS_MAPPING = TunableMapping(
        description=
        '\n        Mapping from course to filter tags for food picker UI.\n        ',
        key_type=TunableEnumWithFilter(
            description=
            '\n            The course associated with the list of filters.\n            ',
            tunable_type=Tag,
            filter_prefixes=['recipe_course'],
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            pack_safe=True,
            binary_type=EnumBinaryExportType.EnumUint32),
        value_type=TunableList(
            description=
            '\n            This list of filter tags for the food picker UI for the course\n            specified.\n            ',
            tunable=TunableEnumWithFilter(
                tunable_type=Tag,
                filter_prefixes=['recipe_category'],
                default=Tag.INVALID,
                invalid_enums=(Tag.INVALID, ),
                pack_safe=True,
                binary_type=EnumBinaryExportType.EnumUint32)),
        key_name='course_key',
        value_name='course_filter_tags',
        tuple_name='CourseToFilterTuple',
        export_modes=ExportModes.ClientBinary)
    CUSTOMER_QUALITY_STAT = TunablePackSafeReference(
        description=
        '\n        The Customer Quality stat applied to food/drink the restaurant customer\n        eats/drinks. This is how we apply buffs to the Sim at the time they\n        consume the food/drink.\n        \n        The Customer Quality value is determined by multiplying the Final\n        Quality To Customer Quality Multiplier (found in Final Quality State\n        Data Mapping) by the Food Difficulty To Customer Quality Multiplier\n        (found in the Ingredient Quality State Data Mapping).\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.STATISTIC))
    CUSTOMER_VALUE_STAT = TunablePackSafeReference(
        description=
        '\n        The Customer Value stat applied to food/drink the restaurant customer\n        eats/drinks. This is how we apply buffs to the Sim at the time they\n        consume the food/drink.\n        \n        The Customer Value value is determined by multiplying the Final Quality\n        To Customer Value Multiplier (found in Final Quality State Data Mapping)\n        by the Markup To Customer Value Multiplier (found in the Markup Data\n        Mapping).\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.STATISTIC))
    RECIPE_DIFFICULTY_DATA_MAPPING = TunableMapping(
        description=
        '\n        A mapping of the recipe difficulty for restaurants to the appropriate\n        data.\n        ',
        key_name='recipe_difficulty',
        key_type=TunableEnumEntry(
            description=
            "\n            The recipe difficulty for chef's at a restaurant.\n            ",
            tunable_type=RecipeDifficulty,
            default=RecipeDifficulty.NORMAL),
        value_name='recipe_difficulty_data',
        value_type=TunableTuple(
            description=
            '\n            The tuning associated with the provided recipe difficulty.\n            ',
            recipe_difficulty_to_final_quality_adder=Tunable(
                description=
                '\n                This value is added to the Ingredient Quality To Final Quality Adder\n                and the Cooking Speed To Final Quality Adder to determine the player-\n                facing recipe quality.\n                ',
                tunable_type=float,
                default=0),
            recipe_difficulty_to_customer_quality_multiplier=Tunable(
                description=
                "\n                This value is multiplied by the Final Quality To Customer\n                Quality Multiplier to determine the customer's perceived quality\n                of the recipe.\n                ",
                tunable_type=float,
                default=1)))
    DEFAULT_INGREDIENT_QUALITY = TunableEnumEntry(
        description=
        '\n        The default ingredient quality for a restaurant.\n        ',
        tunable_type=RestaurantIngredientQualityType,
        default=RestaurantIngredientQualityType.INVALID,
        invalid_enums=(RestaurantIngredientQualityType.INVALID, ))
    INGREDIENT_QUALITY_DATA_MAPPING = TunableMapping(
        description=
        '\n        The mapping between ingredient enum and the ingredient data for\n        that type.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The ingredient type. Organic, normal, lousy, etc...\n            ',
            tunable_type=RestaurantIngredientQualityType,
            default=RestaurantIngredientQualityType.INVALID,
            invalid_enums=(RestaurantIngredientQualityType.INVALID, ),
            binary_type=EnumBinaryExportType.EnumUint32),
        value_type=TunableTuple(
            description=
            '\n            Data associated with this type of ingredient.\n            ',
            ingredient_quality_type_name=TunableLocalizedString(
                description=
                '\n                The localized name of this ingredient used in various places in\n                the UI.\n                '
            ),
            ingredient_quality_to_final_quality_adder=Tunable(
                description=
                '\n                This value is added to the Recipe Difficulty To Final Quality\n                Adder and the Cooking Speed To Final Quality Adder to determine\n                the player-facing recipe quality.\n                ',
                tunable_type=float,
                default=0),
            ingredient_quality_to_restaurant_expense_multiplier=TunableRange(
                description=
                '\n                This value is multiplied by the Base Restaurant Price (found in\n                the Recipe tuning) for each recipe served to determine what the\n                cost is to the restaurant for preparing that recipe.\n                ',
                tunable_type=float,
                default=0.5,
                minimum=0),
            export_class_name='IngredientDataTuple'),
        key_name='ingredient_enum',
        value_name='ingredient_data',
        tuple_name='IngredientEnumDataMappingTuple',
        export_modes=ExportModes.All)
    COOKING_SPEED_DATA_MAPPING = TunableMapping(
        description=
        '\n        A mapping from chef cooking speed to the data associated with that\n        cooking speed.\n        ',
        key_name='cooking_speed_buff',
        key_type=TunableReference(
            description=
            '\n            The cooking speed buff that is applied to the chef.\n            ',
            manager=services.get_instance_manager(sims4.resources.Types.BUFF),
            pack_safe=True),
        value_name='cooking_speed_data',
        value_type=TunableTuple(
            description=
            '\n            The data associated with the tuned cooking speed.\n            ',
            cooking_speed_to_final_quality_adder=Tunable(
                description=
                '\n                This value is added to the Recipe Difficulty To Final Quality\n                Adder and the Ingredient Quality To Final Quality Adder to\n                determine the player-facing recipe quality.\n                ',
                tunable_type=float,
                default=0),
            active_cooking_states_delta=Tunable(
                description=
                '\n                The amount by which to adjust the number of active cooking\n                states the chef must complete before completing the order. For\n                instance, if a -1 is tuned here, the chef will have to complete\n                one less state than normal. Regardless of how the buffs are\n                tuned, the chef will always run at least one state before\n                completing the order.\n                ',
                tunable_type=int,
                default=-1)))
    CHEF_SKILL_TO_FOOD_FINAL_QUALITY_ADDER_DATA = TunableTuple(
        description=
        '\n        Pairs a skill with a curve to determine the additional value to add to\n        the final quality of a food made at an owned restaurant.\n        ',
        skill=TunablePackSafeReference(
            description=
            '\n            The skill used to determine the adder for the final quality of food.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC),
            class_restrictions=('Skill', )),
        final_quality_adder_curve=TunableCurve(
            description=
            "\n            Maps the chef's current level of the tuned skill to a value that\n            will be added to the final quality statistic for food recipes cooked\n            at an owned restaurant.\n            ",
            x_axis_name='Skill Level',
            y_axis_name='Food Final Quality Adder'))
    CHEF_SKILL_TO_DRINK_FINAL_QUALITY_ADDER_DATA = TunableTuple(
        description=
        '\n        Pairs a skill with a curve to determine the additional value to add to\n        the final quality of a drink made at an owned restaurant.\n        ',
        skill=TunablePackSafeReference(
            description=
            '\n            The skill used to determine the adder for the final quality of drinks.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC),
            class_restrictions=('Skill', )),
        final_quality_adder_curve=TunableCurve(
            description=
            "\n            Maps the chef's current level of the tuned skill to a value that\n            will be added to the final quality statistic for drink recipes\n            cooked at an owned restaurant.\n            ",
            x_axis_name='Skill Level',
            y_axis_name='Food Final Quality Adder'))
    FINAL_QUALITY_STATE_DATA_MAPPING = TunableMapping(
        description=
        '\n        A mapping of final quality recipe states (Poor, Normal, Outstanding) to\n        the data associated with that recipe quality.\n        ',
        key_name='recipe_quality_state',
        key_type=TunableReference(
            description=
            '\n            The recipe quality state value.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.OBJECT_STATE),
            class_restrictions='ObjectStateValue',
            pack_safe=True),
        value_name='recipe_quality_state_value_data',
        value_type=TunableTuple(
            description=
            '\n            The data associated with the tuned recipe quality state value.\n            ',
            final_quality_to_customer_quality_multiplier=Tunable(
                description=
                '\n                This value is multiplied by the Recipe Difficulty To Customer\n                Quality Multiplier to determine the Customer Quality State value\n                of the recipe.\n                ',
                tunable_type=float,
                default=1),
            final_quality_to_customer_value_multiplier=Tunable(
                description=
                '\n                This value is multiplied by the Markup To Customer Value\n                Multiplier to determine the value of the Customer Value Stat\n                value of the recipe.\n                ',
                tunable_type=float,
                default=1)))
    PRICE_MARKUP_DATA_MAPPING = TunableMapping(
        description=
        '\n        A mapping of the current price markup of the restaurant to the data\n        associated with that markup.\n        ',
        key_name='markup_multiplier',
        key_type=Tunable(
            description=
            '\n            The markup multiplier. this needs to be in line with the available\n            markups tuned on the restaurant business.\n            ',
            tunable_type=float,
            default=1.5),
        value_name='markup_multiplier_data',
        value_type=TunableTuple(
            description=
            '\n            The data associated with the tuned markup multiplier.\n            ',
            markup_to_customer_value_multiplier=Tunable(
                description='\n                ',
                tunable_type=float,
                default=1)))
    BUSINESS_FUNDS_CATEGORY_FOR_COST_OF_INGREDIENTS = TunableEnumEntry(
        description=
        '\n        When a Chef cooks an order, the restaurant has to pay for the\n        ingredients. This is the category for those expenses.\n        ',
        tunable_type=BusinessFundsCategory,
        default=BusinessFundsCategory.NONE,
        invalid_enums=(BusinessFundsCategory.NONE, ))
    ATTIRE = TunableList(
        description=
        '\n        List of attires player can select to apply to the restaurant.\n        ',
        tunable=TunableEnumEntry(tunable_type=OutfitCategory,
                                 default=OutfitCategory.EVERYDAY,
                                 binary_type=EnumBinaryExportType.EnumUint32),
        export_modes=ExportModes.All)
    UNIFORM_CHEF_MALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit male chef uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    UNIFORM_CHEF_FEMALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit female chef uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    UNIFORM_WAITSTAFF_MALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit waiter uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    UNIFORM_WAITSTAFF_FEMALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit waitress uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    UNIFORM_HOST_MALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit male host uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    UNIFORM_HOST_FEMALE = TunablePackSafeResourceKey(
        description=
        '\n        The SimInfo file to use to edit female host uniforms.\n        ',
        default=None,
        resource_types=(sims4.resources.Types.SIMINFO, ),
        export_modes=ExportModes.All)
    RESTAURANT_VENUE = TunablePackSafeReference(
        description=
        '\n        This is a tunable reference to the type of Venue that will describe\n        a Restaurant. To be used for code references to restaurant venue types\n        in code.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.VENUE))
    HOST_SITUATION = TunablePackSafeReference(
        description=
        '\n        The situation that Sims working as a Host will have.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION))
    WAITSTAFF_SITUATION = TunablePackSafeReference(
        description=
        '\n        The situation that Sims working as a Waiter will have.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION))
    CHEF_SITUATION = TunablePackSafeReference(
        description=
        '\n        The situation that Sims working as a Chef will have.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION))
    HOME_CHEF_SITUATION_TAG = TunableEnumWithFilter(
        description=
        '\n        Tag that we use on all the home chef situations.\n        ',
        tunable_type=Tag,
        filter_prefixes=['situation'],
        default=Tag.INVALID,
        invalid_enums=(Tag.INVALID, ),
        pack_safe=True)
    DINING_SITUATION_TAG = TunableEnumWithFilter(
        description=
        "\n        The tag used to find dining situations. \n        \n        This shouldn't need to be re-tuned after being set initially. If you\n        need to re-tune this you should probably talk to a GPE first.\n        ",
        tunable_type=Tag,
        filter_prefixes=['situation'],
        default=Tag.INVALID,
        pack_safe=True)
    TABLE_FOOD_SLOT_TYPE = TunableReference(
        description=
        '\n        The slot type of the food slot on the dining table.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SLOT_TYPE))
    TABLE_DRINK_SLOT_TYPE = TunableReference(
        description=
        '\n        The slot type of the drink slot on the dining table.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SLOT_TYPE))
    FOOD_AUTONOMY_PREFERENCE = TunableAutonomyPreference(
        description=
        '\n        The Autonomy Preference for the delivered food items.\n        ',
        is_scoring=False)
    DRINK_AUTONOMY_PREFERENCE = TunableAutonomyPreference(
        description=
        '\n        The Autonomy Preference for the delivered drink items.\n        ',
        is_scoring=False)
    CONSUMABLE_FULL_STATE_VALUE = TunableReference(
        description=
        '\n        The Consumable_Full state value. Food in restaurants will be set to\n        this value instead of defaulting to Consumable_Untouched to avoid other\n        Sims from eating your own food.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.OBJECT_STATE),
        class_restrictions=('ObjectStateValue', ))
    CONSUMABLE_EMPTY_STATE_VALUE = TunableReference(
        description=
        "\n        The Consumable_Empty state value. This is the state we'll use to\n        determine if food/drink is empty or not.\n        ",
        manager=services.get_instance_manager(
            sims4.resources.Types.OBJECT_STATE),
        class_restrictions=('ObjectStateValue', ))
    FOOD_DELIVERED_TO_TABLE_NOTIFICATION = TunableUiDialogNotificationSnippet(
        description=
        "\n        The notification shown when the food is delivered to the player's table.\n        "
    )
    FOOD_STILL_ON_TABLE_NOTIFICATION = TunableUiDialogNotificationSnippet(
        description=
        "\n        The notification that the player will see if the waitstaff try and\n        deliver food but there's still food on the table.\n        "
    )
    STAND_UP_INTERACTION = TunableReference(
        description=
        '\n        A reference to sim-stand so that sim-stand can be pushed on every sim\n        that is sitting at a table that is abandoned.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    DEFAULT_MENU = TunableEnumEntry(
        description=
        '\n        The default menu setting for a brand new restaurant.\n        ',
        tunable_type=MenuPresets,
        default=MenuPresets.CUSTOMIZE,
        export_modes=ExportModes.All,
        binary_type=EnumBinaryExportType.EnumUint32)
    SWITCH_SEAT_INTERACTION = TunableReference(
        description=
        '\n        This is a reference to the interaction that gets pushed on whichever Sim\n        is sitting in the seat that the Actor is switching to. The interaction \n        will be pushed onto the sseated Sim and will target the Actor Sims \n        current seat before the switch.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    RECOMMENDED_ORDER_INTERACTION = TunableReference(
        description=
        '\n        This is a reference to the interaction that will get pushed on the active Sim\n        to recommend orders to the Sim AFTER the having gone through the Menu UI.\n        \n        It will continue to retain the previous target.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION),
        pack_safe=True)
    INGREDIENT_PRICE_PERK_MAP = TunableMapping(
        description=
        '\n        Maps the various ingredient price perks with their corresponding\n        discount.\n        ',
        key_name='Ingredient Price Perk',
        key_type=TunableReference(
            description=
            '\n            A perk that gives a tunable multiplier to the price of ingredients\n            for restaurants.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.BUCKS_PERK),
            pack_safe=True),
        value_name='Ingredient Price Multiplier',
        value_type=TunableRange(
            description=
            '\n            If the household has the corresponding perk, this value will be\n            multiplied by the final cost of each recipe to the restaurant.\n            ',
            tunable_type=float,
            default=1,
            minimum=0))
    CUSTOMERS_ORDER_EXPENSIVE_FOOD_PERK_DATA = TunableTuple(
        description=
        '\n        The perk that makes customers order more expensive food, and the off-lot\n        multiplier for that perk.\n        ',
        perk=TunablePackSafeReference(
            description=
            '\n            If the owning household has this perk, customers will pick two dishes to\n            order and then pick the most expensive of the two.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.BUCKS_PERK)),
        off_lot_multiplier=TunableRange(
            description=
            '\n            When calculating off-lot profits, this is applied if the household\n            has this perk.\n            ',
            tunable_type=float,
            default=1.1,
            minimum=1))
    UNOWNED_RESTAURANT_PRICE_MULTIPLIER = TunableRange(
        description=
        '\n        The amount each item in the menu will be multiplied by on unowned\n        restaurant lots.\n        ',
        tunable_type=float,
        default=1.2,
        minimum=0,
        export_modes=ExportModes.All)
    CHEF_NOT_SKILLED_ENOUGH_THRESHOLD = Tunable(
        description=
        '\n        This is the value that a chef must reach when preparing a meal for a\n        customer without displaying the "Chef isn\'t skilled enough to make \n        receiver X" \n        \n        The number that must reach this value is the skill adder\n        of the chef and recipe difficulty adder.\n        ',
        tunable_type=int,
        default=-30)
    CHEF_NOT_SKILLED_ENOUGH_NOTIFICATION = TunableUiDialogNotificationSnippet(
        description=
        '\n        The notification shown when the chef is working on a recipe that is \n        too difficult for their skill.\n        '
    )
    DEFAULT_PROFIT_PER_MEAL_FOR_OFF_LOT_SIMULATION = TunableRange(
        description=
        '\n        This is used as the default profit for a meal for off-lot simulation. Once\n        enough actual meals have been sold, this value becomes irrelevant and\n        the MEAL_COUNT_FOR_OFF_LOT_PROFIT_PER_MEAL tunable comes into use.\n        ',
        tunable_type=int,
        default=20,
        minimum=1)
    MEAL_COUNT_FOR_OFF_LOT_PROFIT_PER_MEAL = TunableRange(
        description=
        '\n        The number of meals to keep a running average of for the profit per meal\n        calculations during off lot simulations.\n        ',
        tunable_type=int,
        default=10,
        minimum=2)
    ADVERTISING_DATA_MAP = TunableMapping(
        description=
        '\n        The mapping between advertising type and the data for that type.\n        ',
        key_name='Advertising_Type',
        key_type=TunableEnumEntry(
            description='\n            The Advertising Type .\n            ',
            tunable_type=BusinessAdvertisingType,
            default=BusinessAdvertisingType.INVALID,
            invalid_enums=(BusinessAdvertisingType.INVALID, ),
            binary_type=EnumBinaryExportType.EnumUint32),
        value_name='Advertising_Data',
        value_type=TunableTuple(
            description=
            '\n            Data associated with this advertising type.\n            ',
            cost_per_hour=TunableRange(
                description=
                '\n                How much, per hour, it costs to use this advertising type.\n                ',
                tunable_type=int,
                default=10,
                minimum=0),
            customer_count_multiplier=TunableRange(
                description=
                '\n                This amount is multiplied by the ideal customer count for owned\n                restaurants.\n                ',
                tunable_type=float,
                default=0.8,
                minimum=0),
            ui_sort_order=TunableRange(
                description=
                '\n                Value representing how map entries will be sorted in the UI.\n                1 represents the first entry.  Avoid duplicate values\n                within the map.\n                ',
                tunable_type=int,
                minimum=1,
                default=1),
            export_class_name='RestaurantAdvertisingData'),
        tuple_name='RestaurantAdvertisingDataMapping',
        export_modes=ExportModes.All)
    TODDLER_SENT_TO_DAYCARE_FOR_RESTAURANTS = TunableUiDialogNotificationSnippet(
        description=
        '\n        The notification shown when a toddler is sent to daycare upon traveling\n        to a restaurant venue.\n        '
    )
    TIME_OF_DAY_TO_CUSTOMER_COUNT_MULTIPLIER_CURVE = TunableCurve(
        description=
        '\n        A curve that lets you tune a specific customer count multiplier\n        based on the time of day. Time of day should range between 0 and 23,\n        0 being midnight.\n        ',
        x_axis_name='time_of_day',
        y_axis_name='customer_count_multiplier')
예제 #24
0
class _ProtestingState(CommonInteractionCompletedSituationState):
    FACTORY_TUNABLES = {
        'attractor_point_identifier':
        TunableEnumWithFilter(
            description=
            '\n            The identifier that will be used to select which attractor points\n            we will use.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            filter_prefixes=('AtPo', ),
            tuning_group=GroupNames.PICKERTUNING),
        'create_sign_interaction':
        TunableReference(
            description=
            '\n            Interaction which will create the protester sign and kick off the protest\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            class_restrictions=('CreateCarriedObjectSuperInteraction', )),
        'fallback_signs':
        TunableList(
            description=
            '\n            The list of possible signs to use if no suitable sign could be found.\n            These should be a fairly generic signs.\n            ',
            tunable=TunableReference(manager=services.definition_manager())),
        'locked_args': {
            'job_and_role_changes': sims4.collections.frozendict(),
            'allow_join_situation': False,
            'time_out': None
        }
    }

    def __init__(self, attractor_point_identifier, create_sign_interaction,
                 fallback_signs, **kwargs):
        super().__init__(**kwargs)
        self._attractor_point_identifier = attractor_point_identifier
        self._create_sign_interaction = create_sign_interaction
        self._fallback_signs = fallback_signs
        self._sign_definition = None

    def set_sign_definition(self, sign_definition):
        self._sign_definition = sign_definition

    def get_sign_definition(self):
        if self._sign_definition is None:
            protestable = self.owner.find_protestable_using_guest_list()
            if protestable is not None:
                self._sign_definition = protestable.sign_definition
        if self._sign_definition is None:
            if self._fallback_signs:
                self._sign_definition = random.choice(self._fallback_signs)
        if not self._sign_definition:
            logger.error(
                'Could not find a sign for the protester to hold.  Please verify tuning on situation: {0}.',
                self.owner)
        return self._sign_definition

    def _get_role_state_overrides(self, sim, job_type, role_state_type,
                                  role_affordance_target):
        context = InteractionContext(sim,
                                     InteractionContext.SOURCE_SCRIPT,
                                     interactions.priority.Priority.High,
                                     insert_strategy=QueueInsertStrategy.NEXT)
        attractor_points = list(
            services.object_manager().get_objects_with_tag_gen(
                self._attractor_point_identifier))
        chosen_point = random.choice(
            attractor_points) if attractor_points else None
        if chosen_point == None:
            logger.warn(
                'No attractor points with tag {} found for situation {}.',
                self._attractor_point_identifier, self.owner)
        sign_definition = self.get_sign_definition()
        if sign_definition is not None:
            sim.push_super_affordance(
                self._create_sign_interaction, chosen_point, context, **{
                    CreateCarriedObjectSuperInteraction.INTERACTION_PARAM_KEY:
                    sign_definition
                })
        return (role_state_type, role_affordance_target)

    def _on_interaction_of_interest_complete(self, **kwargs):
        self.owner._self_destruct()

    def _additional_tests(self, sim_info, event, resolver):
        if sim_info.is_selectable or sim_info in [
                sim.sim_info for sim in self.owner.all_sims_in_situation_gen()
        ]:
            return True
        return False
예제 #25
0
class LaundryTuning:
    GENERATE_CLOTHING_PILE = TunableTuple(description='\n        The tunable to generate clothing pile on the lot. This will be called\n        when we find laundry hero objects on the lot and there is no hamper\n        available.\n        ', loot_to_apply=TunableReference(description='\n            Loot to apply for generating clothing pile.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True), naked_outfit_category=TunableSet(description="\n            Set of outfits categories which is considered naked.\n            When Sim switches FROM these outfits, it won't generate the pile.\n            When Sim switches TO these outfits, it won't apply laundry reward\n            or punishment.\n            ", tunable=TunableEnumEntry(tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY, invalid_enums=(OutfitCategory.CURRENT_OUTFIT,))), no_pile_outfit_category=TunableSet(description="\n            Set of outfits categories which will never generate the pile.\n            When Sim switches FROM or TO these outfits, it won't generate the\n            pile.\n            \n            Laundry reward or punishment will still be applied to the Sim when \n            switching FROM or TO these outfits.\n            ", tunable=TunableEnumEntry(tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY, invalid_enums=(OutfitCategory.CURRENT_OUTFIT,))), no_pile_interaction_tag=TunableEnumWithFilter(description='\n            If interaction does spin clothing change and has this tag, it will\n            generate no clothing pile.\n            ', tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('interaction',)))
    HAMPER_OBJECT_TAGS = TunableTags(description='\n        Tags that considered hamper objects.\n        ', filter_prefixes=('func',))
    LAUNDRY_HERO_OBJECT_TAGS = TunableTags(description='\n        Tags of laundry hero objects. Placing any of these objects on the lot\n        will cause the service to generate clothing pile for each Sims on the\n        household after spin clothing change.\n        ', filter_prefixes=('func',))
    NOT_DOING_LAUNDRY_PUNISHMENT = TunableTuple(description='\n        If no Sim in the household unload completed laundry in specific\n        amount of time, the negative loot will be applied to Sim household \n        on spin clothing change to engage them doing laundry.\n        ', timeout=TunableSimMinute(description="\n            The amount of time in Sim minutes, since the last time they're \n            finishing laundry, before applying the loot.\n            ", default=2880, minimum=1), loot_to_apply=TunableReference(description='\n            Loot defined here will be applied to the Sim in the household\n            on spin clothing change if they are not doing laundry for \n            a while.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True))
    PUT_AWAY_FINISHED_LAUNDRY = TunableTuple(description='\n        The tunable to update laundry service on Put Away finished laundry\n        interaction.\n        ', interaction_tag=TunableEnumWithFilter(description='\n            Tag that represent the put away finished laundry interaction which \n            will update Laundry Service data.\n            ', tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('interaction',)), laundry_condition_states=TunableTuple(description='\n            This is the state type of completed laundry object condition \n            which will aggregate the data to the laundry service.\n            ', condition_states=TunableList(description='\n                A list of state types to be stored on laundry service.\n                ', tunable=TunableStateTypeReference(pack_safe=True), unique_entries=True), excluded_states=TunableList(description='\n                A list of state values of Condition States which will not \n                be added to the laundry service.\n                ', tunable=TunableStateValueReference(pack_safe=True), unique_entries=True)), laundry_condition_timeout=TunableSimMinute(description='\n            The amount of time in Sim minutes that the individual laundry\n            finished conditions will be kept in the laundry conditions \n            aggregate data.\n            ', default=1440, minimum=0), conditions_and_rewards_map=TunableMapping(description='\n            Mapping of laundry conditions and loot rewards.\n            ', key_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.OBJECT_STATE), pack_safe=True), value_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True)))
    PUT_CLOTHING_PILE_ON_HAMPER = TunableTuple(description='\n        The Tunable to directly put generated clothing pile in the hamper.\n        ', chance=TunablePercent(description='\n            The chance that a clothing pile will be put directly in the hamper. \n            Tune the value in case putting clothing pile in hamper every \n            spin-outfit-change feeling excessive.\n            ', default=100), clothing_pile=TunableTuple(description="\n            Clothing pile object that will be created and put into the hamper \n            automatically. \n            \n            You won't see the object on the lot since it will go directly to \n            the hamper. We create it because we need to transfer all of the \n            commodities data and average the values into the hamper precisely.\n            ", definition=TunablePackSafeReference(description='\n                Reference to clothing pile object definition.\n                ', manager=services.definition_manager()), initial_states=TunableList(description='\n                A list of states to apply to the clothing pile as soon as it \n                is created.\n                ', tunable=TunableTuple(description='\n                    The state to apply and optional to decide if the state \n                    should be applied.\n                    ', state=TunableStateValueReference(pack_safe=True), tests=TunableTestSet()))), full_hamper_state=TunableStateValueReference(description='\n            The state of full hamper which make the hamper is unavailable to \n            add new clothing pile in it.\n            ', pack_safe=True), loots_to_apply=TunableList(description='\n            Loots to apply to the hamper when clothing pile is being put.\n            ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True)), tests=TunableTestSet(description='\n            The test to run on the Sim that must pass in order for putting\n            clothing pile automatically to the hamper. These tests will only \n            be run when we have available hamper on the lot.\n            '))
예제 #26
0
class RoleState(HasDependentTunableReference,
                role.role_state_base.RoleStateBase,
                metaclass=HashedTunedInstanceMetaclass,
                manager=services.get_instance_manager(
                    sims4.resources.Types.ROLE_STATE)):
    __qualname__ = 'RoleState'
    INSTANCE_TUNABLES = {
        '_role_priority':
        TunableEnumEntry(
            RolePriority,
            RolePriority.NORMAL,
            description=
            '\n                The priority of this role state.  All the role states with the\n                same priority will all be applied together.  The highest group\n                of priorities is considered the active ones.\n                '
        ),
        '_buffs':
        TunableList(
            buffs.tunable.TunableBuffReference(),
            description=
            '\n                Buffs that will be added to sim when role is active.\n                '
        ),
        '_off_lot_autonomy_buff':
        buffs.tunable.TunableBuffReference(
            description=
            '\n                A buff that prevents autonomy from considering some objects based\n                on the location of the object (e.g. on lot, off lot, within a\n                radius of the sim). \n                In the buff set: Game Effect Modifiers->Autonomy Modifier->Off Lot Autonomy Rule.\n                '
        ),
        'tags':
        TunableSet(
            TunableEnumEntry(Tag, Tag.INVALID),
            description=
            '\n                Tags for the role state for checking role states against a set\n                of tags rather than against a list of role states.\n                '
        ),
        'role_affordances':
        TunableList(
            TunableReference(services.affordance_manager()),
            description=
            "\n                A list of affordances that are available on the sim in this\n                role state. EX: when a Maid is in the maid_role_start\n                role_state, she will have the 'dismiss' and 'fire' affordances\n                when you click on her.\n                "
        ),
        '_on_activate':
        TunableVariant(
            description=
            '\n                Select the autonomy behavior when this role state becomes active on the sim.\n                disabled: Take no action.\n                autonomy_ping: We explicitly force an autonomy ping on the sim.\n                push_affordance: Push the specific affordance on the sim.\n                ',
            locked_args={'disabled': None},
            autonomy_ping=DoAutonomyPingFromRole.TunableFactory(),
            push_affordance=PushAffordanceFromRole.TunableFactory(),
            default='disabled'),
        '_portal_disallowance_tags':
        TunableSet(
            description=
            '\n                A set of tags that define what the portal disallowance tags of\n                this role state are.  Portals that include any of these\n                disallowance tags are considered locked for sims that have this\n                role state.\n                ',
            tunable=TunableEnumWithFilter(
                description=
                '\n                    A single portal disallowance tag.\n                    ',
                tunable_type=tag.Tag,
                default=tag.Tag.INVALID,
                filter_prefixes=tag.PORTAL_DISALLOWANCE_PREFIX)),
        '_allow_npc_routing_on_active_lot':
        Tunable(
            description=
            '\n                If True, then npc in this role will be allowed to route on the\n                active lot.\n                If False, then npc in this role will not be allowed to route on the\n                active lot, unless they are already on the lot when the role\n                state is activated.\n                \n                This flag is ignored for player sims and npcs who live on the\n                active lot.\n                \n                e.g. ambient walkby sims should not be routing on the active lot\n                because that is rude.\n                ',
            tunable_type=bool,
            needs_tuning=True,
            default=True),
        '_only_allow_sub_action_autonomy':
        Tunable(
            description=
            '\n                If True, then the sim in this role will only run sub action\n                autonomy. Full autonomy will not be run.\n                \n                This has very limited uses and can totally hose a sim. Please\n                check with Rez or Sscholl before using it.                \n                ',
            tunable_type=bool,
            needs_tuning=False,
            default=False,
            tuning_filter=FilterTag.EXPERT_MODE)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        for buff_ref in cls.buffs:
            if buff_ref is None:
                logger.error(
                    '{} has empty buff in buff list. Please fix tuning.', cls)
            while buff_ref.buff_type is None:
                logger.error('{} has a buff type not set. Please fix tuning.',
                             cls)

    @classproperty
    def role_priority(cls):
        return cls._role_priority

    @classproperty
    def buffs(cls):
        return cls._buffs

    @classproperty
    def off_lot_autonomy_buff(cls):
        return cls._off_lot_autonomy_buff

    @classproperty
    def role_specific_affordances(cls):
        return cls.role_affordances

    @classproperty
    def allow_npc_routing_on_active_lot(cls):
        return cls._allow_npc_routing_on_active_lot

    @classproperty
    def only_allow_sub_action_autonomy(cls):
        return cls._only_allow_sub_action_autonomy

    @classproperty
    def on_activate(cls):
        return cls._on_activate

    @classproperty
    def portal_disallowance_tags(cls):
        return cls._portal_disallowance_tags

    @classproperty
    def has_full_permissions(cls):
        current_venue = services.get_current_venue()
        if current_venue and current_venue.allow_rolestate_routing_on_navmesh:
            return True
        return not cls._portal_disallowance_tags and cls._allow_npc_routing_on_active_lot

    def _get_target_for_push_affordance(self,
                                        situation_target,
                                        role_affordance_target=None):
        if situation_target == SituationAffordanceTarget.NO_TARGET:
            return
        if situation_target == SituationAffordanceTarget.CRAFTED_OBJECT:
            return role_affordance_target
        logger.error(
            'Unable to resolve target when trying to push affordance on role state {} activate. requested target type was {}',
            self, self._on_activate.target)
예제 #27
0
class RoleState(HasDependentTunableReference,
                role.role_state_base.RoleStateBase,
                metaclass=HashedTunedInstanceMetaclass,
                manager=services.get_instance_manager(
                    sims4.resources.Types.ROLE_STATE)):
    INSTANCE_TUNABLES = {
        '_role_priority':
        TunableEnumEntry(
            RolePriority,
            RolePriority.NORMAL,
            description=
            '\n                The priority of this role state.  All the role states with the\n                same priority will all be applied together.  The highest group\n                of priorities is considered the active ones.\n                '
        ),
        '_buffs':
        TunableList(
            buffs.tunable.TunableBuffReference(pack_safe=True),
            description=
            '\n                Buffs that will be added to sim when role is active.\n                '
        ),
        '_off_lot_autonomy_buff':
        buffs.tunable.TunableBuffReference(
            description=
            'A buff that\n            prevents autonomy from considering some objects based on the\n            location of the object (e.g. on lot, off lot, within a radius of the\n            sim).\n             \n            In the buff set: Game Effect Modifiers->Autonomy Modifier->Off Lot\n            Autonomy Rule.\n            ',
            allow_none=True),
        'tags':
        TunableSet(
            TunableEnumEntry(Tag, Tag.INVALID),
            description=
            '\n                Tags for the role state for checking role states against a set\n                of tags rather than against a list of role states.\n                '
        ),
        'role_affordances':
        TunableList(
            description=
            "\n            A list of affordances that are available on the Sim in this Role\n            State.\n            \n            e.g: when a Maid is in the working Role State, he or she will have\n            the 'Dismiss' and 'Fire' affordances available in the Pie Menu.\n            ",
            tunable=TunableReference(manager=services.affordance_manager(),
                                     class_restrictions=('SuperInteraction', ),
                                     pack_safe=True)),
        'role_target_affordances':
        TunableList(
            description=
            '\n            A list of affordances that are available on other Sims when the\n            actor Sim is in this Role State.\n            \n            e.g. a Sim in a specific Role State could have an "Invite to\n            Situation" interaction available when bringing up other Sims\' Pie\n            Menus.\n            ',
            tunable=TunableReference(manager=services.affordance_manager(),
                                     class_restrictions=('SuperInteraction', ),
                                     pack_safe=True)),
        'preroll_affordances':
        TunableList(
            description=
            '\n            A list of affordances that are available for sims to consider when\n            running pre-roll. Objects related to role can specify preroll\n            autonomy, but there are some roles that may not have an object\n            associated with it\n            \n            e.g. Romance guru in romance festival preroll to an attractor point.\n            ',
            tunable=TunableReference(manager=services.affordance_manager(),
                                     class_restrictions=('SuperInteraction', ),
                                     pack_safe=True)),
        '_on_activate':
        TunableVariant(
            description=
            '\n                Select the autonomy behavior when this role state becomes active on the sim.\n                disabled: Take no action.\n                autonomy_ping: We explicitly force an autonomy ping on the sim.\n                push_affordance: Push the specific affordance on the sim.\n                ',
            locked_args={'disabled': None},
            autonomy_ping=DoAutonomyPingFromRole.TunableFactory(),
            parameterized_autonomy_ping=DoParameterizedAutonomyPingFromRole.
            TunableFactory(),
            push_affordance=PushAffordanceFromRole.TunableFactory(),
            default='disabled'),
        '_portal_disallowance_tags':
        TunableSet(
            description=
            '\n                A set of tags that define what the portal disallowance tags of\n                this role state are.  Portals that include any of these\n                disallowance tags are considered locked for sims that have this\n                role state.\n                ',
            tunable=TunableEnumWithFilter(
                description=
                '\n                    A single portal disallowance tag.\n                    ',
                tunable_type=tag.Tag,
                default=tag.Tag.INVALID,
                filter_prefixes=tag.PORTAL_DISALLOWANCE_PREFIX)),
        '_allow_npc_routing_on_active_lot':
        Tunable(
            description=
            '\n                If True, then npc in this role will be allowed to route on the\n                active lot.\n                If False, then npc in this role will not be allowed to route on the\n                active lot, unless they are already on the lot when the role\n                state is activated.\n                \n                This flag is ignored for player sims and npcs who live on the\n                active lot.\n                \n                e.g. ambient walkby sims should not be routing on the active lot\n                because that is rude.\n                ',
            tunable_type=bool,
            default=True),
        '_autonomy_state_override':
        OptionalTunable(
            description=
            '\n            If tuned, will force role sims into a specific autonomy state.\n            Please consult your GPE partner before using this.\n            ',
            tunable=TunableEnumEntry(tunable_type=AutonomyState,
                                     default=AutonomyState.LIMITED_ONLY,
                                     invalid_enums=(AutonomyState.MEDIUM, )),
            tuning_filter=FilterTag.EXPERT_MODE),
        '_crafting_process_override':
        TunableEnumEntry(
            description=
            '\n                The override option of who to assign ownership of objects made\n                by Sims in this role state.\n                ',
            tunable_type=RoleStateCraftingOwnershipOverride,
            default=RoleStateCraftingOwnershipOverride.NO_OVERRIDE),
        'always_active':
        Tunable(
            description=
            "\n                If set to True, this role will always be allowed to be active\n                when set on a Sim, regardless of whether or not it is \n                lower priority than the Sim's other currently active roles. \n                Use for roles that are important but retuning priority for it \n                and/or other roles isn't feasible.\n                \n                Consult a GPE before you set this to True.\n                This is not to be used lightly and there may be other options\n                like situation exclusivity that can be explored before you\n                go down this route.\n                \n                e.g. Sim is possessed which runs at HIGH priority.\n                Sim wants to go visit an NPC residential lot, which places\n                Sim in NORMAL priority Role_UngreetedPlayerVisitingNPC, which\n                sets portal disallowance and adds specific buffs.\n                \n                We actually want Role_UngreetedPlayerVisitingNPC to run\n                even though the role priority is now HIGH, because \n                otherwise a possessed Sim visiting an NPC would magically\n                be able to route through homes because portal disallowance\n                is removed.\n                ",
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        for buff_ref in cls.buffs:
            if buff_ref is None:
                logger.error(
                    '{} has empty buff in buff list. Please fix tuning.', cls)
            elif buff_ref.buff_type._temporary_commodity_info is not None:
                logger.error(
                    '{} has a buff {} that has a temporary commodity.', cls,
                    buff_ref.buff_type)

    @classproperty
    def role_priority(cls):
        return cls._role_priority

    @classproperty
    def buffs(cls):
        return cls._buffs

    @classproperty
    def off_lot_autonomy_buff(cls):
        return cls._off_lot_autonomy_buff

    @classproperty
    def role_specific_affordances(cls):
        return cls.role_affordances

    @classproperty
    def allow_npc_routing_on_active_lot(cls):
        return cls._allow_npc_routing_on_active_lot

    @classproperty
    def autonomy_state_override(cls):
        return cls._autonomy_state_override

    @classproperty
    def on_activate(cls):
        return cls._on_activate

    @classproperty
    def portal_disallowance_tags(cls):
        return cls._portal_disallowance_tags

    @classproperty
    def has_full_permissions(cls):
        current_venue = services.get_current_venue()
        if current_venue and current_venue.allow_rolestate_routing_on_navmesh:
            return True
        return not cls._portal_disallowance_tags and cls._allow_npc_routing_on_active_lot

    def _get_target_for_push_affordance(self,
                                        situation_target,
                                        situation=None,
                                        role_affordance_target=None):
        if situation_target == SituationAffordanceTarget.NO_TARGET:
            return
        if situation_target == SituationAffordanceTarget.CRAFTED_OBJECT:
            return role_affordance_target
        if situation_target == SituationAffordanceTarget.TARGET_OBJECT and situation is not None:
            return situation.get_target_object()
        if situation_target == SituationAffordanceTarget.CREATED_OBJECT and situation is not None:
            return situation.get_created_object()
        logger.error(
            'Unable to resolve target when trying to push affordance on role state {} activate. requested target type was {}',
            self, self._on_activate.target)

    @classproperty
    def active_household_crafting_override(cls):
        return cls._crafting_process_override == RoleStateCraftingOwnershipOverride.ACTIVE_HOUSEHOLD

    @classproperty
    def lot_owner_crafting_override(cls):
        return cls._crafting_process_override == RoleStateCraftingOwnershipOverride.LOT_OWNER
예제 #28
0
class SpawnPointComponent(Component,
                          HasTunableFactory,
                          AutoFactoryInit,
                          component_name=SPAWN_POINT_COMPONENT,
                          allow_dynamic=True):
    FACTORY_TUNABLES = {
        'spawn_points':
        TunableList(
            description=
            '\n        Spawn points that this object has.\n        ',
            tunable=TunableTuple(
                description=
                '\n            Tuning for spawn point.\n            ',
                spawner_tags=TunableSet(
                    description=
                    "\n                Tags for this spawn point. Sim spawn requests come with a tag.\n                If this spawn point matches a request's tag, then this spawn\n                point is a valid point for the Sim to be positioned at.\n                ",
                    tunable=TunableEnumWithFilter(
                        tunable_type=tag.Tag,
                        filter_prefixes=tag.SPAWN_PREFIX,
                        default=tag.Tag.INVALID,
                        invalid_enums=(tag.Tag.INVALID, ))),
                bone_name=Tunable(
                    description=
                    '\n                The bone on the object to center the spawn point on.\n                ',
                    tunable_type=str,
                    default=''),
                bone_offset=TunableVector3(
                    description=
                    '\n                The offset of the spawn field relative to the bone.\n                ',
                    default=TunableVector3.DEFAULT_ZERO),
                rows=TunableRange(
                    description=
                    '\n                The spawn point has multiple spawn slots arranged in a\n                rectangle. This controls how many rows of spawn slot there are.\n                The total number of Sims that can spawn simultaneously before\n                they start overlapping is number of rows * number of columns.\n                ',
                    tunable_type=int,
                    default=2,
                    minimum=1),
                columns=TunableRange(
                    description=
                    '\n                The spawn point has multiple spawn slots arranged in a\n                rectangle. This controls how many columns of spawn slot there\n                are. The total number of Sims that can spawn simultaneously\n                before they start overlapping is number of rows * number of\n                columns.\n                ',
                    tunable_type=int,
                    default=4,
                    minimum=1),
                scale=TunableRange(
                    description=
                    '\n                The distance between spawn slots.\n                ',
                    tunable_type=float,
                    default=1,
                    minimum=0),
                priority=TunableEnumEntry(
                    description=
                    '\n                The priority of the spawn point.\n                ',
                    tunable_type=SpawnPointPriority,
                    default=SpawnPointPriority.DEFAULT)))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._spawn_points = set()

    def on_add(self):
        for point in self.spawn_points:
            self._add_spawn_point(point)

    def on_remove(self):
        for spawn_point in tuple(self._spawn_points):
            self._remove_spawn_point(spawn_point)

    def _add_spawn_point(self, point):
        spawn_point = DynamicObjectSpawnPoint(self.owner,
                                              point.spawner_tags,
                                              bone_name=point.bone_name,
                                              bone_offset=point.bone_offset,
                                              rows=point.rows,
                                              columns=point.columns,
                                              scale=point.scale,
                                              priority=point.priority)
        self._spawn_points.add(spawn_point)
        services.current_zone().add_dynamic_spawn_point(spawn_point)

    def _remove_spawn_point(self, spawn_point):
        if spawn_point in self._spawn_points:
            self._spawn_points.remove(spawn_point)
            services.current_zone().remove_dynamic_spawn_point(spawn_point)
예제 #29
0
class SituationComplexAdoptionPet(SituationComplexAdoption):
    INSTANCE_TUNABLES = {
        'adoption_officer_job_and_role':
        TunableSituationJobAndRoleState(
            description=
            '\n            The job and role state for the pet adoption officer.\n            '
        ),
        'pet_adoption_candidate_job_and_role':
        TunableSituationJobAndRoleState(
            description=
            '\n            The job and role state for the pets that are adoption candidates.\n            '
        ),
        'has_front_door_arrival_state':
        _HasFrontDoorArrivalState.TunableFactory(
            description=
            '\n            The arrival state for the adoption officer if the lot has a front\n            door.\n            ',
            display_name='1. Has Front Door Arrival State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'has_no_front_door_arrival_state':
        _HasNoFrontDoorArrivalState.TunableFactory(
            description=
            '\n            The arrival state for the adoption officer if the lot does not have\n            a front door.\n            ',
            display_name='1. Has No Front Door Arrival State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'spawn_pets_state':
        _SpawnPetsState.TunableFactory(
            description=
            '\n            The state in which adoption candidate pets are spawned.\n            ',
            display_name='2. Spawn Pets State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'interact_with_pets_state':
        _InteractWithPetsState.TunableFactory(
            description=
            '\n            The state for Sims to interact with adoption candidate pets.\n            ',
            display_name='3. Interact With Pets State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'wait_for_pets_to_despawn_state':
        _WaitForPetsToDespawnState.TunableFactory(
            description=
            '\n            The state where any adoption candidate pets that were not adopted\n            are despawned.\n            ',
            display_name='4. Wait For Pets To Despawn State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'pick_up_adoption_crate_state':
        _PickUpAdoptionCrateState.TunableFactory(
            description=
            '\n            The state for the adoption officer to pick up the adoption crate.\n            ',
            display_name='5. Pick Up Adoption Crate State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'leave_state':
        _LeaveState.TunableFactory(
            description=
            '\n            The state for the adoption officer to leave.\n            ',
            display_name='6. Leave State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'pet_crate_tag':
        TunableEnumWithFilter(
            description=
            '\n            Tag used to find the pet crate object.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            invalid_enums=(Tag.INVALID, ),
            filter_prefixes=('func', )),
        'pet_crate_object_definition':
        TunableReference(
            description=
            '\n            Object definition of the pet crate object that will be created as\n            part of this situation.\n            ',
            manager=services.definition_manager())
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._pet_crate_object_id = None
        reader = self._seed.custom_init_params_reader
        if reader is None:
            self._pet_crate_x = None
            self._pet_crate_y = None
            self._pet_crate_z = None
        else:
            self._pet_crate_x = reader.read_float(PET_CRATE_X, None)
            self._pet_crate_y = reader.read_float(PET_CRATE_Y, None)
            self._pet_crate_z = reader.read_float(PET_CRATE_Z, None)
        self._register_test_event(TestEvent.OnExitBuildBuy)
        self._register_test_event(TestEvent.ObjectDestroyed)

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.OnExitBuildBuy:
            if self.pet_crate is None:
                self._restore_crate()
            self._pet_crate_x = None
            self._pet_crate_y = None
            self._pet_crate_z = None
        if event == TestEvent.ObjectDestroyed:
            if services.current_zone().is_in_build_buy:
                destroyed_obj = resolver.get_resolved_arg('obj')
                if destroyed_obj is not None:
                    if destroyed_obj is self.pet_crate:
                        position = destroyed_obj.position
                        self._pet_crate_x = position.x
                        self._pet_crate_y = position.y
                        self._pet_crate_z = position.z

    def remove_destruction_listener(self):
        self._unregister_test_event(TestEvent.ObjectDestroyed)

    @classmethod
    def _states(cls):
        return (SituationStateData(1,
                                   _HasFrontDoorArrivalState,
                                   factory=cls.has_front_door_arrival_state),
                SituationStateData(
                    2,
                    _HasNoFrontDoorArrivalState,
                    factory=cls.has_no_front_door_arrival_state),
                SituationStateData(3,
                                   _SpawnPetsState,
                                   factory=cls.spawn_pets_state),
                SituationStateData(4,
                                   _InteractWithPetsState,
                                   factory=cls.interact_with_pets_state),
                SituationStateData(5,
                                   _WaitForPetsToDespawnState,
                                   factory=cls.wait_for_pets_to_despawn_state),
                SituationStateData(6,
                                   _PickUpAdoptionCrateState,
                                   factory=cls.pick_up_adoption_crate_state),
                SituationStateData(7, _LeaveState, factory=cls.leave_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.adoption_officer_job_and_role.job,
                 cls.adoption_officer_job_and_role.role_state),
                (cls.pet_adoption_candidate_job_and_role.job,
                 cls.pet_adoption_candidate_job_and_role.role_state)]

    @classmethod
    def default_job(cls):
        pass

    @property
    def pet_crate(self):
        object_manager = services.object_manager()
        pet_crate = None
        if self._pet_crate_object_id is not None:
            pet_crate = object_manager.get(self._pet_crate_object_id)
        if pet_crate is None:
            for obj in services.object_manager().get_objects_with_tag_gen(
                    self.pet_crate_tag):
                pet_crate = obj
                self._pet_crate_object_id = pet_crate.id
                break
        return pet_crate

    def _on_set_sim_job(self, sim, job):
        super()._on_set_sim_job(sim, job)
        self._cur_state.on_set_sim_job(sim, job)

    def _on_remove_sim_from_situation(self, sim):
        super()._on_remove_sim_from_situation(sim)
        if self._cur_state is not None:
            self._cur_state.on_remove_sim_from_situation(sim)

    def adoption_officer_sim(self):
        sim = next(
            self.all_sims_in_job_gen(self.adoption_officer_job_and_role.job),
            None)
        return sim

    def adoptee_pets_gen(self):
        yield from self.all_sims_in_job_gen(
            self.pet_adoption_candidate_job_and_role.job)

    def start_situation(self):
        super().start_situation()
        if services.get_door_service().has_front_door():
            self._change_state(self.has_front_door_arrival_state())
        else:
            self._change_state(self.has_no_front_door_arrival_state())

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        pet_crate = self.pet_crate
        if pet_crate is not None:
            position = pet_crate.position
            writer.write_float(PET_CRATE_X, position.x)
            writer.write_float(PET_CRATE_Y, position.y)
            writer.write_float(PET_CRATE_Z, position.z)

    def load_situation(self):
        result = super().load_situation()
        if result:
            self._restore_crate()
            self._pet_crate_x = None
            self._pet_crate_y = None
            self._pet_crate_z = None
        return result

    def _restore_crate(self):
        if self._pet_crate_x is None:
            return
        obj = create_object(self.pet_crate_object_definition)
        if obj is None:
            return
        position = sims4.math.Vector3(float(self._pet_crate_x),
                                      float(self._pet_crate_y),
                                      float(self._pet_crate_z))
        starting_location = placement.create_starting_location(
            position=position)
        fgl_context = placement.create_fgl_context_for_object(
            starting_location, obj, ignored_object_ids=(obj.id, ))
        (position, orientation) = placement.find_good_location(fgl_context)
        if position is not None and orientation is not None:
            obj.move_to(translation=position, orientation=orientation)
        else:
            obj.destroy()
예제 #30
0
class WalkbyDogWalker(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'group_filter':
        TunableAggregateFilter.TunableReference(
            description=
            '\n            The aggregate filter that we use to find the sims for this\n            situation.\n            '
        ),
        'walk_state':
        WalkbyWalkState.TunableFactory(
            description=
            '\n            A state for getting the Sims to \n            ',
            locked_args={'allow_join_situation': False}),
        'leave_state':
        LeaveState.TunableFactory(
            description=
            '\n            The state for the adoption officer to leave.\n            ',
            locked_args={'allow_join_situation': False}),
        'dog_walker':
        TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                The Situation Job of the dog walker.\n                '
            ),
            initial_role_state=RoleState.TunableReference(
                description=
                '\n                The initial Role State of the dog walker.\n                '
            )),
        'dog':
        TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                The Situation Job of the dog.\n                '
            ),
            initial_role_state=RoleState.TunableReference(
                description=
                '\n                The initial Role State of the dog.\n                '
            )),
        'situation_job_mapping':
        TunableMapping(
            description=
            '\n            A mapping of filter term tag to situation job.\n            \n            The filter term tag is returned as part of the sim filters used to \n            create the guest list for this particular situation.\n            \n            The situation job is the job that the Sim will be assigned to in\n            the background situation.\n            ',
            key_name='filter_tag',
            key_type=TunableEnumEntry(
                description=
                '\n                The filter term tag returned with the filter results.\n                ',
                tunable_type=FilterTermTag,
                default=FilterTermTag.NO_TAG),
            value_name='job',
            value_type=TunableReference(
                description=
                '\n                The job the Sim will receive when added to the this situation.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.SITUATION_JOB))),
        'sim_spawner_tags':
        TunableList(
            description=
            '\n            A list of tags that represent where to spawn Sims for this\n            Situation when they come onto the lot.  This tuning will be used\n            instead of the tuning on the jobs.\n            NOTE: Tags will be searched in order of tuning. Tag [0] has\n            priority over Tag [1] and so on.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=Tag,
                                          default=Tag.INVALID,
                                          filter_prefixes=SPAWN_PREFIX))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        return (SituationStateData(1, GetSimsState),
                SituationStateData(2, WalkbyWalkState, factory=cls.walk_state),
                SituationStateData(3, LeaveState, factory=cls.leave_state))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.dog_walker.situation_job,
                 cls.dog_walker.initial_role_state),
                (cls.dog.situation_job, cls.dog.initial_role_state)]

    @classmethod
    def get_predefined_guest_list(cls):
        guest_list = SituationGuestList(invite_only=True)
        situation_manager = services.get_zone_situation_manager()
        instanced_sim_ids = [
            sim.sim_info.id
            for sim in services.sim_info_manager().instanced_sims_gen()
        ]
        household_sim_ids = [
            sim_info.id
            for sim_info in services.active_household().sim_info_gen()
        ]
        auto_fill_blacklist_walker = situation_manager.get_auto_fill_blacklist(
            sim_job=cls.dog_walker.situation_job)
        auto_fill_blacklist_dog = situation_manager.get_auto_fill_blacklist(
            sim_job=cls.dog.situation_job)
        situation_sims = set()
        for situation in situation_manager.get_situations_by_tags(cls.tags):
            situation_sims.update(situation.invited_sim_ids)
        blacklist_sim_ids = set(
            itertools.chain(situation_sims, instanced_sim_ids,
                            household_sim_ids, auto_fill_blacklist_walker,
                            auto_fill_blacklist_dog))
        filter_results = services.sim_filter_service().submit_matching_filter(
            sim_filter=cls.group_filter,
            allow_yielding=False,
            blacklist_sim_ids=blacklist_sim_ids,
            gsi_source_fn=cls.get_sim_filter_gsi_name)
        if not filter_results:
            return
        if len(filter_results) != cls.group_filter.get_filter_count():
            return
        for result in filter_results:
            job = cls.situation_job_mapping.get(result.tag, None)
            if job is None:
                continue
            guest_list.add_guest_info(
                SituationGuestInfo(result.sim_info.sim_id, job,
                                   RequestSpawningOption.DONT_CARE,
                                   job.sim_auto_invite_allow_priority))
        return guest_list

    def start_situation(self):
        super().start_situation()
        if self._guest_list.guest_info_count != self.group_filter.get_filter_count(
        ):
            self._self_destruct()
        else:
            self._change_state(GetSimsState())

    @classmethod
    def get_sims_expected_to_be_in_situation(cls):
        return cls.group_filter.get_filter_count()

    @classmethod
    def _can_start_walkby(cls, lot_id: int):
        return True

    @classproperty
    def situation_serialization_option(cls):
        return SituationSerializationOption.OPEN_STREETS

    @property
    def _should_cancel_leave_interaction_on_premature_removal(self):
        return True

    def on_all_sims_spawned(self):
        self._change_state(self.walk_state())

    def _issue_requests(self):
        zone = services.current_zone()
        if SpawnPoint.ARRIVAL_SPAWN_POINT_TAG in self.sim_spawner_tags or SpawnPoint.VISITOR_ARRIVAL_SPAWN_POINT_TAG in self.sim_spawner_tags:
            lot_id = zone.lot.lot_id
        else:
            lot_id = None
        spawn_point = zone.get_spawn_point(
            lot_id=lot_id,
            sim_spawner_tags=self.sim_spawner_tags,
            spawn_point_request_reason=SpawnPointRequestReason.SPAWN)
        super()._issue_requests(spawn_point_override=spawn_point)