Ejemplo n.º 1
0
    class _MoodCategory(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'icon_tuning':
            TunableTuple(
                description=
                "\n                A set of icons to associate with this mood category. Different\n                icons are for different states of the mood category's button in\n                the UI.\n                ",
                up_icon=TunableIconAllPacks(),
                down_icon=TunableIconAllPacks(),
                over_icon=TunableIconAllPacks()),
            'tooltip':
            TunableLocalizedStringFactory(
                description=
                '\n                The tooltip for this category in the photography UI.\n                '
            ),
            'mood_param_values':
            TunableList(
                description=
                '\n                A list of mood param values (strings). One of these will be\n                selected at random if a player selects this category in the UI.\n                ',
                tunable=Tunable(tunable_type=str, default=None),
                minlength=1),
            'target_tests_pass_one':
            TunableTestSet(
                description=
                '\n                A set of tests which will be run on every photo target. The\n                target will be the Target Participant and the photographer will\n                be the Actor participant. These tests must pass for at least\n                one target for this mood category to be included.\n                '
            ),
            'target_tests_pass_all':
            TunableTestSet(
                description=
                '\n                A set of tests which will be run on every photo target. The\n                target will be the Target Participant and the photographer will\n                be the Actor participant. These tests must pass for all targets\n                for this mood category to be included.\n                '
            ),
            'test_for_incest':
            Tunable(
                description=
                '\n                If checked, this mood category will be disabled if any pair of\n                targets fails the incest test.\n                ',
                tunable_type=bool,
                default=False)
        }

        def run_tests(self, photographer, targets):
            if len(targets) > 1:
                if self.test_for_incest:
                    target_combinations = itertools.combinations(targets, 2)
                    for (sim_a, sim_b) in target_combinations:
                        if not sim_a.sim_info.incest_prevention_test(
                                sim_b.sim_info):
                            return False
            passed_one = False
            for target in targets:
                resolver = DoubleSimResolver(photographer, target)
                if self.target_tests_pass_one.run_tests(resolver):
                    passed_one = True
                if not self.target_tests_pass_all.run_tests(resolver):
                    return False
            if not passed_one:
                return False
            return True
class AffordanceReferenceScoringModifier(BaseGameEffectModifier):
    FACTORY_TUNABLES = {
        'content_score_bonus':
        Tunable(
            description=
            '\n            When determine content score for affordances and afforance matches\n            tuned here, content score is increased by this amount.\n            ',
            tunable_type=int,
            default=0),
        'success_modifier':
        TunablePercent(
            description=
            '\n            Amount to adjust percent success chance. For example, tuning 10%\n            will increase success chance by 10% over the base success chance.\n            Additive with other buffs.\n            ',
            default=0,
            minimum=-100),
        'affordances':
        TunableList(
            description=
            '\n            A list of affordances that will be compared against.\n            ',
            tunable=TunableReference(manager=services.affordance_manager())),
        'affordance_lists':
        TunableList(
            description=
            '\n            A list of affordance snippets that will be compared against.\n            ',
            tunable=snippets.TunableAffordanceListReference()),
        'interaction_category_tags':
        TunableSet(
            description=
            '\n            This attribute is used to test for affordances that contain any of the tags in this set.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                These tag values are used for testing interactions.\n                ',
                tunable_type=Tag,
                default=Tag.INVALID)),
        'interaction_category_blacklist_tags':
        TunableSet(
            description=
            '\n            Any interaction with a tag in this set will NOT be modiified.\n            Affects display name on a per interaction basis.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                These tag values are used for testing interactions.\n                ',
                tunable_type=Tag,
                default=Tag.INVALID)),
        'pie_menu_parent_name':
        OptionalTunable(
            description=
            '\n            If enabled, we will insert the name into this parent string\n            in the pie menu.  Only affected by test and blacklist tags\n            (for performance reasons)\n            ',
            tunable=TunableLocalizedStringFactory(
                description=
                '\n                A string to wrap the normal interaction name.  Token 0 is actor,\n                Token 1 is the normal name.\n                '
            )),
        'new_pie_menu_icon':
        TunableIconAllPacks(
            description=
            "\n            Icon to put on interactions that pass test (interaction resolver)\n            and don't match blacklist tags.\n            ",
            allow_none=True),
        'basic_extras':
        TunableBasicExtras(
            description=
            '\n            Basic extras to add to interactions that match. \n            '
        ),
        'test':
        event_testing.tests.TunableTestSet(
            description=
            '\n            The test to run to see if the display_name should be\n            overridden. Ors of Ands.\n            '
        )
    }

    def __init__(self,
                 content_score_bonus=0,
                 success_modifier=0,
                 affordances=(),
                 affordance_lists=(),
                 interaction_category_tags=set(),
                 interaction_category_blacklist_tags=set(),
                 pie_menu_parent_name=None,
                 new_pie_menu_icon=None,
                 basic_extras=(),
                 test=None):
        super().__init__(GameEffectType.AFFORDANCE_MODIFIER)
        self._score_bonus = content_score_bonus
        self._success_modifier = success_modifier
        self._affordances = affordances
        self._affordance_lists = affordance_lists
        self._interaction_category_tags = interaction_category_tags
        self._interaction_category_blacklist_tags = interaction_category_blacklist_tags
        self._pie_menu_parent_name = pie_menu_parent_name
        self._new_pie_menu_icon = new_pie_menu_icon
        self._basic_extras = basic_extras
        self._test = test

    def is_type(self, affordance, resolver):
        if affordance is not None:
            if affordance.interaction_category_tags & self._interaction_category_blacklist_tags:
                return False
            if affordance in self._affordances:
                return True
            for affordances in self._affordance_lists:
                if affordance in affordances:
                    return True
            if affordance.interaction_category_tags & self._interaction_category_tags:
                return True
            elif self._test:
                result = False
                try:
                    result = self._test.run_tests(resolver)
                except:
                    pass
                if result:
                    return True
        if self._test:
            result = False
            try:
                result = self._test.run_tests(resolver)
            except:
                pass
            if result:
                return True
        return False

    def get_score_for_type(self, affordance, resolver):
        if self.is_type(affordance, resolver):
            return self._score_bonus
        return 0

    def get_success_for_type(self, affordance, resolver):
        if self.is_type(affordance, resolver):
            return self._success_modifier
        return 0

    def get_new_pie_menu_icon_and_parent_name_for_type(self, affordance,
                                                       resolver):
        if self.is_type(affordance, resolver):
            return (self._new_pie_menu_icon, self._pie_menu_parent_name,
                    self._interaction_category_blacklist_tags)
        return (None, None, None)

    def get_basic_extras_for_type(self, affordance, resolver):
        if self.is_type(affordance, resolver):
            return self._basic_extras
        return []

    def debug_affordances_gen(self):
        for affordance in self._affordances:
            yield affordance.__name__
        for affordnace_snippet in self._affordance_lists:
            yield affordnace_snippet.__name__
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
class FamiliarTracker(SimInfoTracker):
    FAMILIAR_DATA = TunableMapping(description='\n        A mapping between the familiar type and data associated with that familiar type.\n        ', key_type=TunableEnumEntry(description='\n            The type of familiar associated with this data.\n            ', tunable_type=FamiliarType, default=FamiliarType.CAT), value_type=TunableTuple(description='\n            Data associated with a specific familiar type.\n            ', icon=TunableIconAllPacks(description='\n                The icon of the familiar within the picker.\n                '), familiar_type=OptionalTunable(description="\n                The type of familiar this is.\n                Object Familiars have a special object associated with them that is created whenever the Sim is created\n                and has an interaction pushed on the owning Sim to places the pet familiar in a routing formation with\n                the owning Sim.\n                \n                Pet Based Familiars are instead based on Pets and rely on the Pet's autonomy to drive most behavior\n                with the familiar. \n                ", tunable=TunableTuple(description='\n                    Data related to Object Based Familiars.\n                    ', familiar_object=TunablePackSafeReference(description='\n                        The definition of the familiar object that will be created.\n                        ', manager=services.definition_manager()), name_list=TunableEnumEntry(description="\n                        The name list associated with this familiar type.\n                        \n                        Since familiars don't have any specific gender associated with them we always just use Male\n                        names.\n                        ", tunable_type=SimNameType, default=SimNameType.DEFAULT), follow_affordance=TunablePackSafeReference(description='\n                        The specific affordance to follow a familiar.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)), familiar_description=TunableLocalizedStringFactory(description='\n                        The description of this familiar as it appears in the familiar rename dialogs.\n                        '), familiar_token_object=TunableReference(description="\n                        The definition of the familiar token object that will be created and played into the user's\n                        inventory when the familiar is unbound.\n                        ", manager=services.definition_manager(), pack_safe=True)), enabled_by_default=True, disabled_name='pet_based_familiar', enabled_name='object_based_familiar')))
    FAMILIAR_PLACEMENT = _PlacementStrategyLocation.TunableFactory(description="\n        Method for placing the familiar's initial position based on the Sim.\n        ")
    FAMILIAR_ENSEMBLE = TunablePackSafeReference(description='\n        The ensemble to place pet familiars in with their master.\n        ', manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE))
    PET_FAMILIAR_BIT = TunablePackSafeReference(description='\n        The relationship bit to indicate that a pet is a familiar.\n        ', manager=services.get_instance_manager(sims4.resources.Types.RELATIONSHIP_BIT))
    FAMILIAR_SUMMON_FAILURE_NOTIFICATION = UiDialogNotification.TunableFactory(description='\n        A TNS that is displayed when the familiar fails to be summoned.\n        ')
    PET_FAMILIAR_SET_ACTIVE_AFFORDANCE = TunablePackSafeReference(description='\n        An interaction pushed on pet Sims when they are set to be the active familiar.\n        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))
    PET_FAMILIAR_BUFF = TunableMapping(description='\n        A buff added to pet familiars based on age and species.\n        ', key_type=TunableEnumEntry(description='\n            The age that this buff will be applied to.\n            ', tunable_type=Age, default=Age.ADULT), value_type=TunableMapping(key_type=TunableEnumEntry(description='\n                The species that this buff will be applied to.\n                ', tunable_type=SpeciesExtended, default=SpeciesExtended.HUMAN, invalid_enums=(SpeciesExtended.INVALID,)), value_type=TunableBuffReference(description='\n                The buff that will be given to the Familiar of this age/species pair.\n                ', pack_safe=True)))
    ACTIVE_FAMILIAR_BUFF = TunableBuffReference(description='\n        The buff that will be given to the Sim when they have an active familiar.\n        ', pack_safe=True)

    def __init__(self, owner, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._owner = owner
        self._active_familiar_id = None
        self._active_familiar_obj_id = None
        self._familiars = {}
        self._sim_buff_handle = None
        self._pet_buff_handle = None

    def __iter__(self):
        yield from self._familiars.values()

    @property
    def has_familiars(self):
        return len(self._familiars) > 0

    @property
    def active_familiar_id(self):
        return self._active_familiar_id

    @property
    def active_familiar_id_pet_id(self):
        if self._active_familiar_id is None:
            return
        return self._familiars[self._active_familiar_id].pet_familiar_id

    @property
    def active_familiar_type(self):
        if self._active_familiar_id is None:
            return
        return self._familiars[self._active_familiar_id].familiar_type

    def get_active_familiar(self):
        return services.object_manager().get(self._active_familiar_obj_id)

    def get_familiar_name(self, familiar_uid):
        if familiar_uid not in self._familiars:
            logger.error("Attempting to get name of familiar that does not exist within {}'s familiar tracker", self._owner)
            return
        return self._familiars[familiar_uid].name

    def get_familiar_icon(self, familiar_uid):
        if familiar_uid not in self._familiars:
            logger.error("Attempting to get icon of familiar that does not exist within {}'s familiar tracker", self._owner)
            return
        return IconInfoData(FamiliarTracker.FAMILIAR_DATA[self._familiars[familiar_uid].familiar_type].icon)

    def get_familiar_description(self, familiar_uid):
        if familiar_uid not in self._familiars:
            logger.error("Attempting to get description of familiar that does not exist within {}'s familiar tracker", self._owner)
            return
        familiar_type = FamiliarTracker.FAMILIAR_DATA[self._familiars[familiar_uid].familiar_type].familiar_type
        if familiar_type is None:
            logger.error('Attempting to get the description of a Pet familiar.  These familiars do not need descriptions for rename dialogs.')
            return
        return familiar_type.familiar_description

    def bind_familiar(self, familiar_type, pet_familiar=None):
        if pet_familiar is None:
            name = SimSpawner.get_random_first_name(Gender.MALE, sim_name_type_override=FamiliarTracker.FAMILIAR_DATA[familiar_type].familiar_type.name_list)
            pet_familiar_id = None
        else:
            name = None
            pet_familiar_id = pet_familiar.sim_id
            services.relationship_service().add_relationship_bit(self._owner.sim_id, pet_familiar.sim_id, FamiliarTracker.PET_FAMILIAR_BIT)
        familiar_uid = id_generator.generate_object_id()
        new_familiar = FamiliarInfo(familiar_uid, familiar_type, name, pet_familiar_id)
        self._familiars[new_familiar.uid] = new_familiar
        return new_familiar.uid

    def unbind_familiar(self, familiar_uid):
        if familiar_uid not in self._familiars:
            logger.error('Attemting to unbind familiar that is not in the familiar tracker.')
            return
        if self._active_familiar_id is not None and self._active_familiar_id == familiar_uid:
            self.dismiss_familiar()
        familiar_info = self._familiars[familiar_uid]
        pet_familiar_id = familiar_info.pet_familiar_id
        if pet_familiar_id is not None:
            services.relationship_service().remove_relationship_bit(self._owner.sim_id, pet_familiar_id, FamiliarTracker.PET_FAMILIAR_BIT)
        else:
            sim = self._owner.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS)
            if sim is not None:
                familiar_token = create_object(self.FAMILIAR_DATA[familiar_info.familiar_type].familiar_type.familiar_token_object)
                if familiar_token is not None:
                    familiar_token.set_household_owner_id(self._owner.household_id)
                    sim.inventory_component.system_add_object(familiar_token)
                else:
                    logger.error('Attempting to create familiar token on unbind, but failed to do so.')
            else:
                logger.error('Sim is not instanced when unbinding familiar.  The familiar token will not be generated.')
        del self._familiars[familiar_uid]

    def set_familiar_name(self, familiar_id, new_name):
        if familiar_id is None:
            if self._active_familiar_id:
                logger.error('Trying to set the name of a familiar with both no specified familiar nor no active familiar.')
                return
            familiar_id = self._active_familiar_id
        self._familiars[familiar_id].name = new_name

    def _on_familiar_summon_failure(self, error_message, familiar_object=None, exc=None, warn=False):
        if exc is not None:
            logger.exception(error_message, exc=exc)
        elif warn:
            logger.warn(error_message)
        else:
            logger.error(error_message)
        if familiar_object is not None:
            familiar_object.destroy()
        self._active_familiar_id = None
        self._active_familiar_obj_id = None
        resolver = SingleSimResolver(self._owner)
        dialog = self.FAMILIAR_SUMMON_FAILURE_NOTIFICATION(self._owner, resolver)
        dialog.show_dialog()

    def _on_familiar_follow_interaction_finished_prematurely(self, interaction):
        if interaction.is_finishing_naturally or interaction.has_been_reset:
            return
        self._active_familiar_id = None
        self._active_familiar_obj_id = None
        if self._sim_buff_handle is not None:
            self._owner.remove_buff(self._sim_buff_handle)
            self._sim_buff_handle = None

    def _create_and_establish_familiar_link(self):
        sim = self._owner.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS_EXCEPT_UNINITIALIZED)
        if sim is None:
            return
        familiar_type = self._familiars[self._active_familiar_id].familiar_type
        familiar_data = FamiliarTracker.FAMILIAR_DATA[familiar_type]
        if familiar_data.familiar_type is None:
            try:
                self._active_familiar_obj_id = self._familiars[self._active_familiar_id].pet_familiar_id
                pet_familiar_sim_info = services.sim_info_manager().get(self._active_familiar_obj_id)
                buff_info_to_add = self.PET_FAMILIAR_BUFF[pet_familiar_sim_info.age][pet_familiar_sim_info.extended_species]
                self._pet_buff_handle = pet_familiar_sim_info.add_buff(buff_info_to_add.buff_type, buff_reason=buff_info_to_add.buff_reason)
                pet_familiar = services.object_manager().get(self._active_familiar_obj_id)
                if services.current_zone().is_zone_running:
                    if pet_familiar is None:
                        services.current_zone().venue_service.active_venue.summon_npcs((pet_familiar_sim_info,), NPCSummoningPurpose.BRING_PLAYER_SIM_TO_LOT)
                    else:
                        context = InteractionContext(pet_familiar, InteractionContext.SOURCE_SCRIPT, Priority.Critical, insert_strategy=QueueInsertStrategy.NEXT)
                        pet_familiar.push_super_affordance(FamiliarTracker.PET_FAMILIAR_SET_ACTIVE_AFFORDANCE, None, context)
            except Exception as e:
                self._on_familiar_summon_failure('Exception encountered when trying to create familiar.  Deactivating familiar.', familiar_object=pet_familiar, exc=e)
            if self._sim_buff_handle is None:
                self._sim_buff_handle = self._owner.add_buff(self.ACTIVE_FAMILIAR_BUFF.buff_type, buff_reason=self.ACTIVE_FAMILIAR_BUFF.buff_reason)
            return
        familiar = services.object_manager().get(self._active_familiar_obj_id)
        if familiar is None:
            try:
                familiar_obj_def = familiar_data.familiar_type.familiar_object
                familiar = create_object(familiar_obj_def)
                if familiar is None:
                    self._on_familiar_summon_failure('Failure to create familiar object.  Deactivating familiar.')
                    return
                resolver = SingleSimResolver(self._owner)
                if not FamiliarTracker.FAMILIAR_PLACEMENT.try_place_object(familiar, resolver):
                    self._on_familiar_summon_failure('Failure to create familiar object.  Deactivating familiar.', familiar_object=familiar, warn=True)
                    return
                self._active_familiar_obj_id = familiar.id
            except Exception as e:
                self._on_familiar_summon_failure('Exception encountered when trying to create familiar.  Deactivating familiar.', familiar_object=familiar, exc=e)
                return
        context = InteractionContext(sim, InteractionSource.SCRIPT, Priority.Critical, insert_strategy=QueueInsertStrategy.NEXT)
        result = sim.push_super_affordance(familiar_data.familiar_type.follow_affordance, familiar, context)
        if not result:
            self._on_familiar_summon_failure('Failed to push familiar follow interaction.  Deactivating Familiar.', familiar_object=familiar)
            return
        result.interaction.register_on_finishing_callback(self._on_familiar_follow_interaction_finished_prematurely)
        if self._sim_buff_handle is None:
            self._sim_buff_handle = self._owner.add_buff(self.ACTIVE_FAMILIAR_BUFF.buff_type, buff_reason=self.ACTIVE_FAMILIAR_BUFF.buff_reason)

    def remove_active_pet_familiar_buff(self):
        if self._pet_buff_handle is None:
            return
        pet_sim_info = services.sim_info_manager().get(self._familiars[self._active_familiar_id].pet_familiar_id)
        if pet_sim_info is None:
            self._pet_buff_handle = None
            return
        pet_sim_info.remove_buff(self._pet_buff_handle)
        self._pet_buff_handle = None

    def set_active_familiar(self, familiar_uid):
        if familiar_uid not in self._familiars:
            logger.error("Attempting to set a familiar as active that isn't an actual familiar.")
            return
        if self._active_familiar_obj_id is not None:
            active_familiar_obj = services.object_manager().get(self._active_familiar_obj_id)
            if active_familiar_obj is not None and not active_familiar_obj.is_sim:
                active_familiar_obj.destroy()
            self.remove_active_pet_familiar_buff()
            self._active_familiar_obj_id = None
        self._active_familiar_id = familiar_uid
        self._create_and_establish_familiar_link()

    def dismiss_familiar(self):
        if self._active_familiar_id is None:
            return
        if self._sim_buff_handle is not None:
            self._owner.remove_buff(self._sim_buff_handle)
            self._sim_buff_handle = None
        self.remove_active_pet_familiar_buff()
        familiar = self.get_active_familiar()
        if familiar is None:
            if self._familiars[self._active_familiar_id].pet_familiar_id is None:
                logger.error("Attempting to dismiss a familiar that is active, but doesn't have a familiar object.")
            self._active_familiar_obj_id = None
            self._active_familiar_id = None
            return
        if not familiar.is_sim:
            owner_sim = self._owner.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS)
            if owner_sim is None:
                familiar.destroy()
            else:
                follow_affordance = self.FAMILIAR_DATA[self._familiars[self._active_familiar_id].familiar_type].familiar_type.follow_affordance
                sim_interactions = owner_sim.get_all_running_and_queued_interactions()
                for interaction in sim_interactions:
                    if interaction.affordance is follow_affordance:
                        interaction.cancel(FinishingType.NATURAL, cancel_reason_msg='User changed familiars.')
        self._active_familiar_obj_id = None
        self._active_familiar_id = None

    def on_sim_startup(self):
        if self._active_familiar_id is None:
            return
        if self._sim_buff_handle is None:
            self._sim_buff_handle = self._owner.add_buff(self.ACTIVE_FAMILIAR_BUFF.buff_type, buff_reason=self.ACTIVE_FAMILIAR_BUFF.buff_reason)
        self._create_and_establish_familiar_link()

    def on_sim_removed(self):
        if self._active_familiar_id is None:
            return
        if self._sim_buff_handle is not None:
            self._owner.remove_buff(self._sim_buff_handle)
            self._sim_buff_handle = None
        self.remove_active_pet_familiar_buff()
        active_familiar = self.get_active_familiar()
        if active_familiar is None or active_familiar.is_sim:
            return
        active_familiar.destroy()

    def on_household_member_removed(self):
        sim_info_manager = services.sim_info_manager()
        for familiar_data in tuple(self._familiars.values()):
            pet_familiar = sim_info_manager.get(familiar_data.pet_familiar_id)
            if pet_familiar is None:
                continue
            if pet_familiar.household_id == self._owner.household_id:
                continue
            services.relationship_service().remove_relationship_bit(self._owner.sim_id, familiar_data.pet_familiar_id, FamiliarTracker.PET_FAMILIAR_BIT)
            if self._active_familiar_id == familiar_data.uid:
                self.dismiss_familiar()
            del self._familiars[familiar_data.uid]
            return

    def save(self):
        data = SimObjectAttributes_pb2.PersistableFamiliarTracker()
        if self._active_familiar_id is not None:
            data.active_familiar_uid = self._active_familiar_id
        for familiar_info in self._familiars.values():
            with ProtocolBufferRollback(data.familiars) as entry:
                familiar_info.save_familiar_info(entry)
        return data

    def load(self, data):
        if data.HasField('active_familiar_uid'):
            self._active_familiar_id = data.active_familiar_uid
        sim_info_manager = services.sim_info_manager()
        for familiar_data in data.familiars:
            if familiar_data.HasField('familiar_name'):
                familiar_name = familiar_data.familiar_name
            else:
                familiar_name = None
            if familiar_data.HasField('pet_familiar_id'):
                pet_familiar_id = familiar_data.pet_familiar_id
            else:
                pet_familiar_id = None
            try:
                loaded_familiar = FamiliarInfo(familiar_data.familiar_uid, FamiliarType(familiar_data.familiar_type), familiar_name, pet_familiar_id=pet_familiar_id)
            except Exception as e:
                logger.exception('Exception encountered when trying to load familiar.  Skipping familiar.', exc=e)
                if pet_familiar_id is not None:
                    services.relationship_service().remove_relationship_bit(self._owner.sim_id, pet_familiar_id, FamiliarTracker.PET_FAMILIAR_BIT)
            self._familiars[familiar_data.familiar_uid] = loaded_familiar

    def on_all_sim_infos_loaded(self):
        sim_info_manager = services.sim_info_manager()
        for familiar_data in tuple(self._familiars.values()):
            pet_familiar_id = familiar_data.pet_familiar_id
            if pet_familiar_id is None:
                continue
            pet_familiar = sim_info_manager.get(pet_familiar_id)
            if pet_familiar is not None and pet_familiar.household_id == self._owner.household_id:
                continue
            self.unbind_familiar(familiar_data.uid)

    @classproperty
    def _tracker_lod_threshold(cls):
        return SimInfoLODLevel.ACTIVE

    def on_lod_update(self, old_lod, new_lod):
        if new_lod < self._tracker_lod_threshold:
            self.dismiss_familiar()
            self._clean_up()
        elif old_lod < self._tracker_lod_threshold:
            sim_msg = services.get_persistence_service().get_sim_proto_buff(self._owner.id)
            if sim_msg is not None:
                self.load(sim_msg.attributes.familiar_tracker)

    def _clean_up(self):
        self._active_familiar_obj_id = None
        self._active_familiar_id = None
        self._familiars.clear()
        self._familiars = None
Ejemplo n.º 5
0
class HolidayDefinition(HasTunableReference,
                        HolidayDefinitionDisplayMixin,
                        metaclass=HashedTunedInstanceMetaclass,
                        manager=services.get_instance_manager(
                            sims4.resources.Types.HOLIDAY_DEFINITION)):
    POSSIBLE_ICONS = TunableList(
        description=
        '\n        A list of tunable icons that can be selected for a holiday.\n        ',
        tunable=TunableIconAllPacks(
            description=
            '\n            An icon that can be selected for a holiday.\n            ',
            export_modes=ExportModes.All),
        export_modes=ExportModes.All)
    INSTANCE_TUNABLES = {
        'traditions':
        TunableList(
            description=
            '\n            List of default traditions for this holiday.\n            ',
            tunable=TunableReference(
                description=
                '\n                A default tradition for this holiday.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.HOLIDAY_TRADITION)),
            unique_entries=True,
            maxlength=5),
        'time_off_work':
        Tunable(
            description=
            '\n            If checked, Sims will have the day off from work, both part time\n            and full time.\n            ',
            tunable_type=bool,
            default=False),
        'time_off_school':
        Tunable(
            description=
            '\n            If checked, Sims will have the day off from school for the holiday.\n            ',
            tunable_type=bool,
            default=False),
        'can_be_modified':
        Tunable(
            description=
            '\n            If checked, this holiday can be modified by the player.\n            ',
            tunable_type=bool,
            default=False),
        'decoration_preset':
        OptionalTunable(
            description=
            '\n            The decoration preset that this holiday is set to by default.\n            \n            If disabled, this holiday does not do decorations.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                Types.LOT_DECORATION_PRESET))),
        'calendar_alert_description':
        OptionalTunable(
            description=
            '\n            If tuned, there will be a calendar alert description.\n            ',
            tunable=TunableLocalizedStringFactory(
                description=
                '\n                Description that shows up in the calendar alert.\n                0 - Holiday Name\n                '
            )),
        'audio_sting':
        TunableResourceKey(
            description='\n            The sound to play.\n            ',
            default=None,
            resource_types=(sims4.resources.Types.PROPX, ))
    }

    @classproperty
    def display_name(cls):
        return cls._display_data.instance_display_name

    @classproperty
    def display_icon(cls):
        return cls._display_data.instance_display_icon
class PickServiceNpcSuperInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'service_npcs':
        TunableList(
            description=
            '\n            A list of the service npcs that will show up in the dialog picker\n            ',
            tunable=TunableTuple(
                description=
                '\n                Tuple of service npcs data about those NPCs being pickable.\n                ',
                service_npc=TunableReference(
                    description=
                    '\n                    The service npcs that will show up in the picker.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.SERVICE_NPC),
                    class_restrictions=(ServiceNpcHireable, ),
                    pack_safe=True),
                already_hired_tooltip=TunableLocalizedStringFactory(
                    description=
                    '\n                    Tooltip that displays if the service has already been\n                    hired.\n                    '
                ),
                tests=event_testing.tests.TunableGlobalTestSet(
                    description=
                    '\n                    A set of tests that determine if this service npc will show up\n                    as available or greyed out in the picker.\n                    '
                )),
            tuning_group=GroupNames.PICKERTUNING),
        'non_service_npcs':
        TunableList(
            description=
            "\n            A List of non service NPCs that can be hired using the\n            'Hire A Service' UI.\n            ",
            tunable=TunableTuple(
                description=
                "\n                The Data needed to display the non service NPC in the \n                'Hire A Service' UI.\n                ",
                icon=TunableIconAllPacks(
                    description=
                    "\n                    The icon to be displayed in 'Hire a Service' UI\n                    ",
                    tuning_group=GroupNames.UI),
                name=TunableLocalizedStringFactory(
                    description=
                    "\n                    The name to be displayed for this NPC in the 'Hire a Service'\n                    UI.\n                    "
                ),
                cost_string=TunableVariant(
                    description=
                    '\n                    When enabled, the tuned string will be shown as the cost\n                    of hiring this NPC.\n                    ',
                    cost_amount=Tunable(
                        description='\n                        ',
                        tunable_type=int,
                        default=0),
                    no_cost_string=TunableLocalizedStringFactory(
                        description=
                        "\n                        The description to be used for this NPC in the \n                        if there isn't a cost associated with it\n                        "
                    ),
                    locked_args={'disabled': None},
                    default='disabled'),
                hire_interaction=TunableReference(
                    description=
                    '\n                    The affordance to push the sim making the call when hiring this\n                    service npc from a picker dialog from the phone.\n                    ',
                    manager=services.affordance_manager(),
                    pack_safe=True),
                tests=event_testing.tests.TunableGlobalTestSet(
                    description=
                    '\n                    A set of global tests that are always run before other tests. All\n                    tests must pass in order for the interaction to run.\n                    '
                ),
                free_service_traits=TunableList(
                    description=
                    '\n                    If any Sim in the household has one of these traits, the \n                    non service npc will be free.\n                    ',
                    tunable=TunableReference(manager=services.trait_manager(),
                                             pack_safe=True),
                    unique_entries=True))),
        'display_price_flat_rate':
        TunableLocalizedStringFactory(
            description=
            '\n            Formatting for cost of the service if it has just a one time flat fee.\n            Parameters: 0 is flat rate cost of the service\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_price_hourly_cost':
        TunableLocalizedStringFactory(
            description=
            '\n            Formatting for cost of the service if it is purely hourly\n            Parameters: 0 is hourly cost of the service\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_price_fee_and_hourly_cost':
        TunableLocalizedStringFactory(
            description=
            '\n            Formatting for cost of the service if it has an upfront cost AND an\n            hourly cost\n            Parameters: 0 is upfront cost of service. 1 is hourly cost of service\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_price_free':
        TunableLocalizedString(
            description=
            '\n            Description text if the service has zero upfront cost and zero hourly cost.\n            ',
            tuning_group=GroupNames.PICKERTUNING)
    }

    def _run_interaction_gen(self, timeline):
        self._show_picker_dialog(self.sim, target_sim=self.sim)
        return True
        yield

    class _ServiceNpcRecurringPair:
        def __init__(self, service_npc_type, recurring):
            self.service_npc_type = service_npc_type
            self.recurring = recurring
            self.__name__ = '{} recurring: {}'.format(self.service_npc_type,
                                                      self.recurring)

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = cls if inst is None else inst
        service_npc_data_tuples = [
            service_npc_data for service_npc_data in inst_or_cls.service_npcs
        ]
        for service_npc_data in service_npc_data_tuples:
            service_npc_type = service_npc_data.service_npc
            household = context.sim.household
            service_record = household.get_service_npc_record(
                service_npc_type.guid64, add_if_no_record=False)
            is_enabled = service_record is None or not service_record.hired
            if not is_enabled:
                tooltip = service_npc_data.already_hired_tooltip
            else:
                tooltip = None
            allows_recurring = service_npc_type._recurring is not None
            display_name = service_npc_type.display_name if not allows_recurring else service_npc_type._recurring.one_time_name
            tag = PickServiceNpcSuperInteraction._ServiceNpcRecurringPair(
                service_npc_type, recurring=False)
            if any(
                    sim.trait_tracker.has_any_trait(
                        service_npc_type.free_service_traits)
                    for sim in household.sim_info_gen()):
                display_description = inst_or_cls.display_price_free
            elif not not service_npc_type.cost_up_front > 0 and service_npc_type.cost_hourly > 0:
                display_description = inst_or_cls.display_price_fee_and_hourly_cost(
                    service_npc_type.cost_up_front,
                    service_npc_type.cost_hourly)
            elif service_npc_type.cost_up_front > 0:
                display_description = inst_or_cls.display_price_flat_rate(
                    service_npc_type.cost_up_front)
            elif service_npc_type.cost_hourly > 0:
                display_description = inst_or_cls.display_price_hourly_cost(
                    service_npc_type.cost_hourly)
            else:
                display_description = inst_or_cls.display_price_free
            resolver = inst_or_cls.get_resolver(target,
                                                context,
                                                inst_or_cls,
                                                search_for_tooltip=True)
            result = service_npc_data.tests.run_tests(resolver,
                                                      search_for_tooltip=True)
            is_enabled = result == TestResult.TRUE
            tooltip = result.tooltip
            if not (not is_enabled or not is_enabled) and tooltip is None:
                continue
            row = ui.ui_dialog_picker.ObjectPickerRow(
                is_enable=is_enabled,
                name=display_name,
                icon=service_npc_type.icon,
                row_description=display_description,
                tag=tag,
                row_tooltip=tooltip)
            yield row
            if allows_recurring:
                tag = PickServiceNpcSuperInteraction._ServiceNpcRecurringPair(
                    service_npc_type, recurring=True)
                row = ui.ui_dialog_picker.ObjectPickerRow(
                    is_enable=is_enabled,
                    name=service_npc_type._recurring.recurring_name,
                    icon=service_npc_type.icon,
                    row_description=display_description,
                    tag=tag)
                yield row
        for entry in cls.non_service_npcs:
            if any(
                    sim.trait_tracker.has_any_trait(entry.free_service_traits)
                    for sim in household.sim_info_gen()):
                cost_string = inst_or_cls.display_price_free
            else:
                cost_string = inst_or_cls._get_cost_string(entry)
            resolver = inst_or_cls.get_resolver(target,
                                                context,
                                                inst_or_cls,
                                                search_for_tooltip=True)
            result = entry.tests.run_tests(resolver, search_for_tooltip=True)
            if not result and result.tooltip is None:
                continue
            row = ui.ui_dialog_picker.ObjectPickerRow(
                is_enable=result == TestResult.TRUE,
                name=entry.name(),
                icon=entry.icon,
                row_description=cost_string,
                tag=entry,
                row_tooltip=result.tooltip)
            yield row

    @flexmethod
    def _get_cost_string(cls, inst, entry):
        cost_string = entry.cost_string
        if cost_string is None:
            return
        if isinstance(cost_string, int):
            return LocalizationHelperTuning.get_for_money(cost_string)
        return cost_string()

    def on_choice_selected(self, choice_tag, **kwargs):
        tag = choice_tag
        if tag is not None:
            if isinstance(
                    tag,
                    PickServiceNpcSuperInteraction._ServiceNpcRecurringPair):
                tag.service_npc_type.on_chosen_from_service_picker(
                    self, recurring=tag.recurring)
            elif tag.hire_interaction is not None:
                push_affordance = self.generate_continuation_affordance(
                    tag.hire_interaction)
                for aop in push_affordance.potential_interactions(
                        self.sim, self.context):
                    aop.test_and_execute(self.context)
Ejemplo n.º 7
0
class ShowScholarshipDynamicSignLoot(BaseLootOperation):
    SCHOLARSHIP_INFORMATION_SIGN = TunableTuple(
        title=TunableLocalizedString(
            description=
            '\n            The title to be shown on top of view.\n            '
        ),
        display_image=TunableIconAllPacks(
            description=
            '\n             The image for this view display.\n             '),
        background_image=TunableIconAllPacks(
            description=
            '\n             The background image for this view display.\n             '
        ),
        sub_infos=TunableList(
            description=
            '\n            The sub info to be shown on the bottom of the view.\n            ',
            tunable=TunableTuple(
                description=
                '\n                A single info.\n                ',
                name=TunableLocalizedString(
                    description=
                    '\n                    The name of this info.\n                    '
                ),
                desc=TunableLocalizedString(
                    description=
                    '\n                    The description of this info.\n                    '
                ),
                icon=TunableIconAllPacks(
                    description=
                    '\n                    The Icon that represents this info.\n                    '
                ))))

    def display_scholarship_info(self, subject):
        if self.SCHOLARSHIP_INFORMATION_SIGN.display_image is None or self.SCHOLARSHIP_INFORMATION_SIGN.background_image is None:
            logger.error(
                'Attempting to show scholarship sign to ({}) when content is None.',
                subject)
            return
        sign_info = UI_pb2.DynamicSignView()
        sign_info.name = self.SCHOLARSHIP_INFORMATION_SIGN.title
        sign_info.image = sims4.resources.get_protobuff_for_key(
            self.SCHOLARSHIP_INFORMATION_SIGN.display_image)
        sign_info.background_image = sims4.resources.get_protobuff_for_key(
            self.SCHOLARSHIP_INFORMATION_SIGN.background_image)
        for sub_info in self.SCHOLARSHIP_INFORMATION_SIGN.sub_infos:
            with ProtocolBufferRollback(sign_info.activities) as activity_msg:
                if sub_info.icon is None:
                    logger.error(
                        'Attempting to show scholarship sign to ({}) when sub_info icon is None.',
                        subject)
                    continue
                activity_msg.name = sub_info.name
                activity_msg.description = sub_info.desc
                activity_msg.icon = create_icon_info_msg(
                    IconInfoData(sub_info.icon))
        distributor = Distributor.instance()
        distributor.add_op(
            subject.sim_info,
            GenericProtocolBufferOp(Operation.DYNAMIC_SIGN_VIEW, sign_info))
        services.get_event_manager().process_event(
            TestEvent.ScholarshipInfoSignShown, sim_info=subject)

    def _apply_to_subject_and_target(self, subject, target, resolver):
        if subject is None:
            logger.error(
                'Trying to perform ScholarshipDynamicSignView op but subject is None. Resolver ({}).',
                resolver)
            return
        self.display_scholarship_info(subject)
Ejemplo n.º 8
0
class Situation(BaseSituation, HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SITUATION)):

    @staticmethod
    def _verify_situation_level_tuning(instance_class, tunable_name, source, bronze, gold, silver, tin):
        gold_reward = gold.reward
        if gold_reward is not None and gold_reward.reward_description is None:
            logger.error('Situation "{}" has a Gold tier reward that has no Reward Description tuned. Bronze and Silver are optional, but Gold requires a description.', source, owner='asantos')

    INSTANCE_SUBCLASSES_ONLY = True
    NPC_HOSTED_SITUATION_AGE_WEIGHTING = TunableMapping(description='\n        A map of ages to weights when determining which sim in the household\n        will be selected to receive an invitation.\n        ', key_name='age', key_type=TunableEnumEntry(description='\n            The age of a possible invitee that will be mapped to a weight.\n            ', tunable_type=Age, default=Age.ADULT), value_name='weight', value_type=TunableRange(description='\n            The weight a sim of this age will be chosen to have an event run\n            on them.\n            ', tunable_type=int, default=1, minimum=1))
    INSTANCE_TUNABLES = {'category': TunableEnumEntry(description='\n                The Category that the Situation belongs to.\n                ', tunable_type=SituationCategoryUid, default=SituationCategoryUid.DEFAULT, tuning_group=GroupNames.UI), 'load_open_street_situation_with_selectable_sim': Tunable(description='\n            If the situation has selectable sims, set to True to ensure the\n            situation can load from the open street, False otherwise.\n            \n            Note: The Serialization Option also determines save/load strategy.\n            Check with GPE to verify the situation save/load behavior.\n            ', tunable_type=bool, default=False), '_display_name': TunableLocalizedString(description='\n                Display name for situation\n                ', allow_none=True, tuning_group=GroupNames.UI), 'situation_description': TunableLocalizedString(description='\n                Situation Description\n                ', allow_none=True, tuning_group=GroupNames.UI), 'entitlement': OptionalTunable(description='\n            If enabled, this situation is locked by an entitlement. Otherwise,\n            this situation is available to all players.\n            ', tunable=TunableEntitlement(description='\n                Entitlement required to plan this event.\n                ', tuning_group=GroupNames.UI)), '_default_job': TunableReference(description='\n                The default job for Sims in this situation\n                ', manager=services.situation_job_manager(), allow_none=True), '_resident_job': SituationJob.TunableReference(description='\n                The job to assign to members of the host sims household.\n                Make sure to use the in_family filter term in the filter\n                of the job you reference here.\n                It is okay if this tunable is None.\n                ', allow_none=True), '_icon': TunableResourceKey(description='\n                Icon to be displayed in the situation UI.\n                ', resource_types=sims4.resources.CompoundTypes.IMAGE, default=None, allow_none=True, tuning_group=GroupNames.UI), 'calendar_icon': TunableIconAllPacks(description='\n            Icon to be displayed in the calendar UI.\n            ', allow_none=True, tuning_group=GroupNames.UI), 'calendar_alert_description': OptionalTunable(description='\n            If tuned, there will be a calendar alert description.\n            ', tunable=TunableLocalizedString(description='\n                Description that shows up in the calendar alert.\n                ')), '_level_data': TunableTuple(tin=TunableSituationLevel(description='\n                    Tuning for the Tin level of this situation.  This level has\n                    a score delta of 0 as it is considered the default level\n                    of any situation.\n                    ', locked_args={'medal': SituationMedal.TIN, 'score_delta': 0}), bronze=TunableSituationLevel(description='\n                    Tuning for the Bronze level of this situation.\n                    ', locked_args={'medal': SituationMedal.BRONZE}), silver=TunableSituationLevel(description='\n                    Tuning for the Silver level of this situation.\n                    ', locked_args={'medal': SituationMedal.SILVER}), gold=TunableSituationLevel(description='\n                    Tuning for the Gold level of this situation.\n                    ', locked_args={'medal': SituationMedal.GOLD}), description='\n                    Tuning for the different situation levels and rewards that\n                    are associated with them.\n                    ', verify_tunable_callback=_verify_situation_level_tuning), 'job_display_ordering': OptionalTunable(description='\n            An optional list of the jobs in the order you want them displayed\n            in the Plan an Event UI.\n            ', tunable=TunableList(tunable=TunableReference(manager=services.situation_job_manager())), tuning_group=GroupNames.UI), 'recommended_job_object_notification': ui.ui_dialog_notification.UiDialogNotification.TunableFactory(description='\n            The notification that is displayed when one or more recommended objects\n            for a job are missing.\n            ', locked_args={'text': None}), 'recommended_job_object_text': TunableLocalizedStringFactory(description='\n            The text of the notification that is displayed when one or more recommended\n            objects for a job are missing.\n            \n            The localization tokens for the Text field are:\n            {0.String} = bulleted list of strings for the missing objects\n            ', allow_none=True), '_buff': TunableBuffReference(description='\n                Buff that will get added to sim when commodity is at this\n                current state.\n                ', allow_none=True), '_cost': Tunable(description='\n                The cost of this situation\n                ', tunable_type=int, default=0), 'exclusivity': TunableEnumEntry(description='\n            Defines the exclusivity category for the situation which is used to prevent sims assigned\n            to this situation from being assigned to situations from categories excluded by this\n            category and vice versa.\n            ', tunable_type=situations.bouncer.bouncer_types.BouncerExclusivityCategory, default=situations.bouncer.bouncer_types.BouncerExclusivityCategory.NORMAL), 'main_goal': TunableReference(description='The main goal of the situation. e.g. Get Married.', manager=services.get_instance_manager(sims4.resources.Types.SITUATION_GOAL), allow_none=True, tuning_group=GroupNames.GOALS), 'main_goal_audio_sting': TunableResourceKey(description='\n                The sound to play when the main goal of this situation\n                completes.\n                ', default=None, resource_types=(sims4.resources.Types.PROPX,), tuning_group=GroupNames.AUDIO), '_main_goal_visibility_test': OptionalTunable(description='\n                If enabled then the main goal of the situation will not be\n                visible until this test passes.  If the state of this test no\n                longer becomes true then the main gaol will not become\n                invisible again.\n                \n                Ex. A hospital emergency starting triggers the visiblity of the\n                main goal within the active career event situation.\n                \n                IMPORTANT: The nature of this test can cause performance\n                problems.\n                ', tunable=TunableMainGoalVisibilityTestVariant(), tuning_group=GroupNames.GOALS), 'minor_goal_chains': TunableList(description='\n                A list of goal sets, each one starting a chain of goal sets, for selecting minor goals.\n                The list is in priority order, first being the most important.\n                At most one goal will be selected from each chain.\n                ', tunable=situations.situation_goal_set.SituationGoalSet.TunableReference(), tuning_group=GroupNames.GOALS), 'force_invite_only': Tunable(description='\n                If True, the situation is invite only. Otherwise, it is not.\n                For a date situation, this would be set to True.\n                ', tunable_type=bool, default=False), 'creation_ui_option': TunableEnumEntry(description='\n                Determines if the situation can be created from the Plan Event\n                UI triggered from the phone.\n                \n                NOT_AVAILABLE - situation is not available in the creation UI.\n                \n                AVAILABLE - situation is available in the creation UI.\n                \n                DEBUG_AVAILABLE - situation is only available in the UI if\n                you have used the |situations.allow_debug_situations command\n                \n                SPECIFIED_ONLY - situation is available in the creation UI if\n                that UI is tuned to only look at a subset of situations.\n                ', tunable_type=SituationCreationUIOption, default=SituationCreationUIOption.AVAILABLE, tuning_group=GroupNames.UI), 'audio_sting_on_start': TunableResourceKey(description='\n                The sound to play when the Situation starts.\n                ', default=None, resource_types=(sims4.resources.Types.PROPX,), tuning_group=GroupNames.AUDIO), 'background_audio': OptionalTunable(description='\n                If enabled then we will play audio in the background while this\n                user facing situation is running.\n                ', tunable=TunableResourceKey(description='\n                    Audio that will play throughout the situation in the background\n                    and will end at the end of the situation.\n                    ', default=None, resource_types=(sims4.resources.Types.PROPX,)), tuning_group=GroupNames.AUDIO), 'duration': TunableSimMinute(description='\n                How long the situation will last in sim minutes. 0 means forever.\n                ', default=60), 'duration_randomizer': TunableSimMinute(description="\n            A random time between 0 and this tuned time will be added to the\n            situation's duration.\n            ", default=0, minimum=0), 'max_participants': Tunable(description='\n                Maximum number of Sims the player is allowed to invite to this Situation.\n                ', tunable_type=int, default=16, tuning_group=GroupNames.UI), '_initiating_sim_tests': TunableSituationInitiationSet('\n            A set of tests that will be run on a sim attempting to initiate a\n            situation.  If these tests do not pass than this situation will not\n            be able to be chosen from the UI.\n            '), 'targeted_situation': OptionalTunable(description='\n                If enabled, the situation can be used as a targeted situation,\n                such as a Date.\n                ', tunable=TargetedSituationSpecific.TunableFactory()), 'compatible_venues': TunableList(description='\n                In the Plan an Event UI, lots that are these venues will be\n                added to the list of lots on which the player can throw the\n                event. The player can always choose their own lot and lots of\n                their guests.\n                ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.VENUE), pack_safe=True, tuning_group=GroupNames.VENUES)), 'venue_region_must_be_compatible': Tunable(description='\n                If enabled, venues will only be considered if they are in a\n                region that is compatible with the current region (regions with\n                at least one shared tag).\n                ', tunable_type=bool, default=False), 'venue_invitation_message': OptionalTunable(description='\n            If enabled, show a dialog when the situation tries to start on a\n            venue.\n            ', tunable=UiDialogOkCancel.TunableFactory(description="\n                The message that will be displayed when this situation tries to\n                start for the venue.\n                \n                Two additional tokens are passed in: the situation's name and\n                the job's name.\n                "), tuning_group=GroupNames.VENUES), 'venue_situation_player_job': TunableReference(description="\n                The job that the player will be put into when they join in a\n                user_facing special situation at a venue.\n                \n                Note: This must be tuned to allow this situation to be in a\n                venue's special event schedule. The job also must be a part of\n                the Situation.\n                ", manager=services.get_instance_manager(sims4.resources.Types.SITUATION_JOB), allow_none=True, tuning_group=GroupNames.VENUES), '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)), '_relationship_between_job_members': TunableList(description="\n                Whenever a sim joins either job_x or job_y, the sim is granted \n                the tuned relationship bit with every sim in the other job. The\n                relationship bits are added and remain as long as the sims are\n                assigned to the tuned pair of jobs.\n                \n                This creates a relationship between the two sims if one does not exist.\n                \n                E.g. Date situation uses this feature to add bits to the sims'\n                relationship in order to drive autonomous behavior during the \n                lifetime of the date. \n                ", tunable=TunableTuple(job_x=SituationJob.TunableReference(), job_y=SituationJob.TunableReference(), relationship_bits_to_add=TunableSet(description='\n                        A set of RelationshipBits to add to relationship between the sims.\n                        ', tunable=RelationshipBit.TunableReference())), tuning_group=GroupNames.TRIGGERS), '_implies_greeted_status': Tunable(description='\n                If checked then a sim, in this situation, on a residential lot\n                they do not own, is consider greeted on that lot.\n                \n                Greeted status related design documents:\n                //depot/Sims4Projects/docs/Design/Gameplay/HouseholdState/Ungreeted_Lot_Behavior_DD.docx\n                //depot/Sims4Projects/docs/Design/Gameplay/Simulation/Active Lot Changing Edge Cases.docx\n                ', tunable_type=bool, default=False), 'screen_slam_no_medal': OptionalTunable(description='\n            Screen slam to show when this situation is completed and no\n            medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'screen_slam_bronze': OptionalTunable(description='\n            Screen slam to show when this situation is completed and a\n            bronze medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'screen_slam_silver': OptionalTunable(description='\n            Screen slam to show when this situation is completed and a\n            silver medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'screen_slam_gold': OptionalTunable(description='\n            Screen slam to show when this situation is completed and a\n            bronze medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'time_jump': TunableSituationTimeJumpVariant(description='\n            Determine how the situation handles the zone time being different on\n            load than what it was on save. This is primarily useful for\n            commercial venue background situations and career event situations.\n            ', tuning_group=GroupNames.SPECIAL_CASES), 'can_remove_sims_from_work': Tunable(description='\n            If checked then this situation will cause sims to end work early\n            when they are on the guest list. If unchecked, it will not. This\n            option will not affect active career situations or NPC career\n            situations like tending the bar.\n            ', tunable_type=bool, default=True, tuning_group=GroupNames.SPECIAL_CASES), '_survives_active_household_change': Tunable(description='\n            If checked then this situation will load even if the active\n            household has changed since it was saved. It will attempt to\n            restore Sims to their saved jobs. This is primarily useful for\n            commercial venue background situations.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_maintain_sims_consistency': Tunable(description="\n            If checked, Sims in the saved situation that were pushed home \n            because they had been saved in the zone for many Sim hours will \n            be back. Otherwise, we will find replacement.\n            \n            Ex. We don't want to replace Butler with new Sim if previous\n            Butler is no longer in the lot.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_hidden_scoring_override': Tunable(description='\n            If checked then even if this situation has its scoring disabled it\n            still will count score and provide rewards to the player.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_ensemble': OptionalTunable(description='\n            If enabled then we will keep Sims in a specific ensemble for the\n            duration of the situation.\n            ', tunable=TunableTuple(description='\n                Tunables for putting Sims into ensembles.\n                ', ensemble_type=TunableReference(description='\n                    The type of Ensemble to put the sims into.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE), pack_safe=True), remove_before_add=Tunable(description='\n                    If checked then before we add the Sim to the ensemble we\n                    will remove them from from ensembles of the specified type.\n                    This can be used to force Sims into only an ensemble of\n                    Sims in this situation.\n                    ', tunable_type=bool, default=False), ignore_situation_removal=Tunable(description='\n                    If checked then we will not remove the Sim from the\n                    ensemble of this type when the Sim is removed from the\n                    situation.\n                    ', tunable_type=bool, default=True), ensemble_option=TunableEnumEntry(description='\n                    How we want to add Sims to an ensemble:\n                    ONLY_WITHIN_SITUATION: Put the Sims in this situation into\n                    an ensemble of this type.  Every time a sim is added we\n                    try and do this so if the user destroys the ensemble and\n                    then another Sim is spawned for it the ensemble will be\n                    recreated.\n                    \n                    ADD_TO_ACTIVE_HOUSEHOLD: Every time a Sim is spawned for\n                    this situation they are put into an ensemble with the\n                    instanced active household.  This is useful if you want to\n                    put the Sims in a situation with someone who is not in it. \n                    \n                    ADD_TO_HOST: Every time a Sim is spawned for this situation\n                    they are put into an ensemble with the host of the\n                    situation.  This is useful if you want to put the Sims in\n                    a situation with someone who is not in it.\n                    ', tunable_type=EnsembleOption, default=EnsembleOption.ONLY_WITHIN_SITUATION)), tuning_group=GroupNames.SPECIAL_CASES), 'blocks_super_speed_three': Tunable(description='\n            If enabled, this situation will block any requests to go into super\n            speed 3.\n            ', tunable_type=bool, default=False), 'travel_request_behavior': TunableSituationTravelRequestBehaviorVariant(description='\n            Define how this situation handles incoming travel requests from\n            other situations when running as user-facing.\n            '), 'allowed_in_super_speed_3': Tunable(description="\n            If enabled, this situation will skip the super speed 3 rules and\n            be allowed to trigger at that speed.\n            This will only affect walkby's as they are the only restricted\n            by speed 3.\n            ", tunable_type=bool, default=False), 'should_send_on_lot_home_in_super_speed_3': Tunable(description='\n            If enabled, on_lot sims in this situation will not prevent SS3.  If\n            SS3 is triggered they will be sent home.\n            ', tunable_type=bool, default=False), 'super_speed3_replacement_speed': OptionalTunable(description='\n            If enabled and this situation blocks super speed 3, the situation will attempt to request\n            this speed if it is running when super speed 3 tries to kick in.\n            ', tunable=TunableEnumEntry(tunable_type=ClockSpeedMode, invalid_enums=(ClockSpeedMode.PAUSED, ClockSpeedMode.INTERACTION_STARTUP_SPEED, ClockSpeedMode.SUPER_SPEED3), default=ClockSpeedMode.SPEED3)), 'weight_multipliers': TunableMultiplier.TunableFactory(description="\n            Tunable tested multiplier to apply to any weight this situation\n            might have as part of a Situation Curve. These multipliers will be\n            applied globally anywhere this situation is tuned as part of a\n            situation curve (i.e. Walkby Tuning) so it should only be used in\n            cases where you ALWAYS want this multiplier applied.\n            \n            NOTE: You can still tune more multipliers on the individual walk by\n            instances. The multipliers will all stack together.\n            \n            *IMPORTANT* The only participants that work are ones\n            available globally, such as Lot and ActiveHousehold. Only\n            use these participant types or use tests that don't rely\n            on any, such as testing all objects via Object Criteria\n            test or testing active zone with the Zone test.\n            ", locked_args={'base_value': 1}), 'disallows_curfew_violation': Tunable(description='\n            If this is checked then the Sim is unable to violate curfew while\n            in the situation. If this is not checked then the Sim can vioalte\n            curfew as normal.\n            ', tunable_type=bool, default=False), 'suppress_scoring_progress_bar': Tunable(description='\n            If this is checked, UI will no longer show the scoring progress bar\n            and instead show the situation name in its stead.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'highlight_first_incomplete_minor_goal': Tunable(description='\n            If this is checked, we will tell user-facing Situation UI \n            to highlight the first uncompleted minor goal set.\n            \n            Note that gameplay currently does not guard against being able \n            to complete the other goal sets in the situation currently, \n            so the goalsets should be tuned in such a manner \n            that they do not overlap.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.UI), '_use_spawner_tags_on_travel': Tunable(description='\n            If checked the situation will spawn sims according to its job spawner tags\n            instead of always defaulting to the Arrival spawner.\n            ', tunable_type=bool, default=False)}
    SITUATION_SCORING_REMOVE_INSTANCE_TUNABLES = ('main_goal', '_main_goal_visibility_test', 'minor_goal_chains', 'main_goal_audio_sting', 'highlight_first_incomplete_minor_goal', 'suppress_scoring_progress_bar', '_level_data', 'screen_slam_gold', 'screen_slam_silver', 'screen_slam_bronze', 'screen_slam_no_medal')
    SITUATION_START_FROM_UI_REMOVE_INSTANCE_TUNABLES = ('_cost', 'compatible_venues', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'max_participants', '_initiating_sim_tests', '_icon', 'entitlement', 'job_display_ordering')
    SITUATION_USER_FACING_REMOVE_INSTANCE_TUNABLES = ('_display_name', 'travel_request_behavior', 'recommended_job_object_notification', 'recommended_job_object_text', 'situation_description')
    NON_USER_FACING_REMOVE_INSTANCE_TUNABLES = ('_buff', 'targeted_situation', '_resident_job', '_relationship_between_job_members', 'audio_sting_on_start', 'background_audio', 'force_invite_only') + SITUATION_SCORING_REMOVE_INSTANCE_TUNABLES + SITUATION_START_FROM_UI_REMOVE_INSTANCE_TUNABLES + SITUATION_USER_FACING_REMOVE_INSTANCE_TUNABLES
    SITUATION_EVENT_REMOVE_INSTANCE_TUNABLES = ('_buff', '_cost', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', '_main_goal_visibility_test', 'minor_goal_chains', 'highlight_first_incomplete_minor_goal', 'suppress_scoring_progress_bar', 'max_participants', '_initiating_sim_tests', '_icon', 'targeted_situation', '_resident_job', 'situation_description', 'job_display_ordering', 'entitlement', '_relationship_between_job_members', 'main_goal_audio_sting', 'audio_sting_on_start', 'background_audio', '_level_data', '_display_name', 'screen_slam_gold', 'screen_slam_silver', 'screen_slam_bronze', 'screen_slam_no_medal', 'force_invite_only', 'recommended_job_object_notification', 'recommended_job_object_text', 'travel_request_behavior')
    situation_level_data = None
    SituationLevel = collections.namedtuple('SituationLevel', ['min_score_threshold', 'level_data'])

    @classmethod
    def _tuning_loaded_callback(cls):
        cls.situation_level_data = []
        current_score = cls._level_data.tin.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.tin))
        current_score += cls._level_data.bronze.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.bronze))
        current_score += cls._level_data.silver.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.silver))
        current_score += cls._level_data.gold.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.gold))

    @classmethod
    def _verify_tuning_callback(cls):
        if cls._resident_job is not None and cls._resident_job.filter is None:
            logger.error('Resident Job: {} has no filter,', cls._resident_job, owner='manus')
        if cls.targeted_situation is not None and (cls.targeted_situation.target_job is None or cls.targeted_situation.actor_job is None):
            logger.error('target_job and actor_job are required if targeted_situation is enabled.', owner='manus')
        tuned_jobs = frozenset(cls.get_tuned_jobs())
        for job_relationships in cls.relationship_between_job_members:
            if job_relationships.job_x not in tuned_jobs:
                logger.error('job_x: {} has relationship tuning but is not functionally used in situation {}.', job_relationships.job_x, cls, owner='manus')
            if job_relationships.job_y not in tuned_jobs:
                logger.error('job_y: {} has relationship tuning but is not functionally used in situation {}.', job_relationships.job_y, cls, owner='manus')
            if len(job_relationships.relationship_bits_to_add) == 0:
                logger.error("relationship_bits_to_add cannot be empty for situation {}'s job pairs {} and {}.", cls, job_relationships.job_x, job_relationships.job_y, owner='manus')
            else:
                for bit in job_relationships.relationship_bits_to_add:
                    if bit is None:
                        logger.error("relationship_bits_to_add cannot contain empty bit for situation {}'s job pairs {} and {}.", cls, job_relationships.job_x, job_relationships.job_y, owner='manus')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._duration_alarm_handle = None
        self._goal_tracker = None
        self._dynamic_goals = self._seed.extra_kwargs.get('dynamic_goals', None)

    @classproperty
    def allow_user_facing_goals(cls):
        return cls.main_goal is not None or len(cls.minor_goal_chains) > 0

    @classmethod
    def level_data_gen(cls):
        for level in cls.situation_level_data:
            yield level

    @classmethod
    def fake_perform_job(cls):
        pass

    @classmethod
    def get_level_data(cls, medal:SituationMedal=SituationMedal.TIN):
        if cls.situation_level_data is None:
            return
        return cls.situation_level_data[medal].level_data

    @classmethod
    def get_level_min_threshold(cls, medal:SituationMedal=SituationMedal.TIN):
        if cls.situation_level_data is None:
            return
        return cls.situation_level_data[medal].min_score_threshold

    @classmethod
    def get_level_icon(cls, medal:SituationMedal=SituationMedal.TIN):
        if cls.situation_level_data is None:
            return
        return cls.situation_level_data[medal].level_data.icon

    @classmethod
    def get_possible_zone_ids_for_situation(cls, host_sim_info=None, guest_ids=None):
        possible_zones = []
        venue_manager = services.get_instance_manager(sims4.resources.Types.VENUE)
        venue_service = services.current_zone().venue_service
        for venue_tuning in cls.compatible_venues:
            if venue_tuning.is_residential:
                if host_sim_info is not None:
                    home_zone_id = host_sim_info.household.home_zone_id
                    home_venue_tuning = venue_manager.get(build_buy.get_current_venue(home_zone_id))
                    if home_venue_tuning.is_residential:
                        possible_zones.append(home_zone_id)
                if guest_ids is not None:
                    for guest_id in guest_ids:
                        guest_id = int(guest_id)
                        guest_info = services.sim_info_manager().get(guest_id)
                        if guest_info is not None:
                            guest_zone_id = guest_info.household.home_zone_id
                            if guest_zone_id is not None and guest_zone_id and guest_zone_id not in possible_zones:
                                guest_venue_tuning = venue_manager.get(build_buy.get_current_venue(guest_zone_id))
                                if guest_venue_tuning.is_residential:
                                    possible_zones.append(guest_zone_id)
                            travel_group = guest_info.travel_group
                            if travel_group is not None:
                                travel_group_zone_id = travel_group.zone_id
                                if travel_group_zone_id is not None and travel_group_zone_id and travel_group_zone_id not in possible_zones:
                                    possible_zones.append(travel_group_zone_id)
                    else:
                        possible_zones.extend(venue_service.get_zones_for_venue_type_gen(venue_tuning))
            else:
                possible_zones.extend(venue_service.get_zones_for_venue_type_gen(venue_tuning))
        return possible_zones

    @classmethod
    def default_job(cls):
        return cls._default_job

    @classmethod
    def resident_job(cls):
        return cls._resident_job

    @classmethod
    def get_prepopulated_job_for_sims(cls, sim, target_sim_id=None):
        if target_sim_id and cls.targeted_situation is not None:
            sim_info = services.sim_info_manager().get(target_sim_id)
            if sim_info is None:
                return
            else:
                prepopulated = [(sim.id, cls.targeted_situation.actor_job.guid64), (target_sim_id, cls.targeted_situation.target_job.guid64)]
                return prepopulated

    def _display_role_objects_notification(self, sim, bullets):
        text = self.recommended_job_object_text(bullets)
        notification = self.recommended_job_object_notification(sim, text=lambda *_, **__: text)
        notification.show_dialog()

    @property
    def pie_menu_icon(self):
        return self._pie_menu_icon

    @classproperty
    def display_name(self):
        return self._display_name

    @property
    def description(self):
        return self.situation_description

    @classproperty
    def icon(self):
        return self._icon

    @property
    def start_audio_sting(self):
        return self.audio_sting_on_start

    @property
    def audio_background(self):
        return self.background_audio

    def get_target_object(self):
        pass

    def get_created_object(self):
        pass

    @property
    def end_audio_sting(self):
        current_level = self.get_level()
        level_data = self.get_level_data(current_level)
        if level_data is not None and level_data.audio_sting_on_end is not None:
            return level_data.audio_sting_on_end
        else:
            return

    @classproperty
    def relationship_between_job_members(cls):
        return cls._relationship_between_job_members

    @classproperty
    def implies_greeted_status(cls):
        return cls._implies_greeted_status

    @classmethod
    def cost(cls):
        return cls._cost

    @classproperty
    def survives_active_household_change(cls):
        return cls._survives_active_household_change

    @classproperty
    def maintain_sims_consistency(cls):
        return cls._maintain_sims_consistency

    def _get_duration(self):
        if self._seed.duration_override is not None:
            return self._seed.duration_override
        return self.duration + random.randint(0, self.duration_randomizer)

    def _get_remaining_time(self):
        if self._duration_alarm_handle is None:
            return
        return self._duration_alarm_handle.get_remaining_time()

    def _get_remaining_time_for_gsi(self):
        return self._get_remaining_time()

    def _get_remaining_time_in_minutes(self):
        time_span = self._get_remaining_time()
        if time_span is None:
            return 0
        return time_span.in_minutes()

    def _get_goal_tracker(self):
        return self._goal_tracker

    def _save_custom(self, seed):
        super()._save_custom(seed)
        if self._goal_tracker is not None:
            self._goal_tracker.save_to_seed(seed)

    def start_situation(self):
        super().start_situation()
        self._set_duration_alarm()
        if self.is_user_facing:
            if self.should_track_score:
                if self._dynamic_goals is None:
                    self._goal_tracker = situations.situation_goal_tracker.SituationGoalTracker(self)
                else:
                    self._goal_tracker = situations.dynamic_situation_goal_tracker.DynamicSituationGoalTracker(self)

    def _load_situation_states_and_phases(self):
        super()._load_situation_states_and_phases()
        self._set_duration_alarm()
        if not self._seed.goal_tracker_seedling:
            return
        if self._seed.goal_tracker_seedling.goal_tracker_type == GoalTrackerType.STANDARD_GOAL_TRACKER:
            self._goal_tracker = situations.situation_goal_tracker.SituationGoalTracker(self)
        elif self._seed.goal_tracker_seedling.goal_tracker_type == GoalTrackerType.DYNAMIC_GOAL_TRACKER:
            self._goal_tracker = situations.dynamic_situation_goal_tracker.DynamicSituationGoalTracker(self)

    def change_duration(self, duration):
        if not self.is_running:
            logger.error("Trying to change the duration of a situation {} that's not running.", self)
        self._set_duration_alarm(duration_override=duration)
        if self.is_user_facing:
            self.add_situation_duration_change_op()

    def _set_duration_alarm(self, duration_override=None):
        if duration_override is not None:
            duration = duration_override
        else:
            duration = self._get_duration()
        self.set_end_time(duration)
        if duration > 0:
            if self._duration_alarm_handle is not None:
                alarms.cancel_alarm(self._duration_alarm_handle)
            self._duration_alarm_handle = alarms.add_alarm(self, clock.interval_in_sim_minutes(duration), self._situation_timed_out)

    def _cancel_duration_alarm(self):
        if self.is_user_facing:
            logger.error('Canceling duration alarm for a User-Facing Situation {}', self, owner='rmccord')
        if self._duration_alarm_handle is not None:
            alarms.cancel_alarm(self._duration_alarm_handle)

    def pre_destroy(self):
        pass

    def _destroy(self):
        if self._duration_alarm_handle is not None:
            alarms.cancel_alarm(self._duration_alarm_handle)
        if self._goal_tracker is not None:
            self._goal_tracker.destroy()
            self._goal_tracker = None
        super()._destroy()

    def _situation_timed_out(self, _):
        logger.debug('Situation time expired: {}', self)
        self._self_destruct()

    @classmethod
    def is_situation_available(cls, initiating_sim, target_sim_id=0):
        is_targeted = cls.targeted_situation is not None and cls.targeted_situation.target_job is not None
        if is_targeted and target_sim_id:
            if not cls.targeted_situation.target_job.can_sim_be_given_job(target_sim_id, initiating_sim.sim_info):
                return TestResult(False)
        elif (target_sim_id == 0) != (is_targeted == False):
            return TestResult(False)
        single_sim_resolver = event_testing.resolver.SingleSimResolver(initiating_sim.sim_info)
        return cls._initiating_sim_tests.run_tests(single_sim_resolver)

    @classmethod
    def get_predefined_guest_list(cls):
        pass

    @classmethod
    def is_venue_location_valid(cls, zone_id):
        compatible_region = services.current_region() if cls.venue_region_must_be_compatible else None
        return services.current_zone().venue_service.is_zone_valid_for_venue_type(zone_id, cls.compatible_venues, compatible_region=compatible_region)

    @classmethod
    def get_venue_location(cls):
        compatible_region = services.current_region() if cls.venue_region_must_be_compatible else None
        (zone_id, _) = services.current_zone().venue_service.get_zone_and_venue_type_for_venue_types(cls.compatible_venues, compatible_region=compatible_region)
        return zone_id

    @classmethod
    def has_venue_location(cls):
        compatible_region = services.current_region() if cls.venue_region_must_be_compatible else None
        return services.current_zone().venue_service.has_zone_for_venue_type(cls.compatible_venues, compatible_region=compatible_region)

    @classproperty
    def main_goal_visibility_test(cls):
        return cls._main_goal_visibility_test

    @classproperty
    def _ensemble_data(cls):
        return cls._ensemble

    @property
    def should_track_score(self):
        return self.scoring_enabled or self._hidden_scoring_override

    @property
    def should_give_rewards(self):
        return self.scoring_enabled or self._hidden_scoring_override

    def is_in_joinable_state(self):
        return True

    @property
    def custom_event_keys(self):
        return [type(self)] + list(self.tags)

    @classproperty
    def use_spawner_tags_on_travel(cls):
        return cls._use_spawner_tags_on_travel
Ejemplo n.º 9
0
class NotebookTuning:
    NOTEBOOK_CATEGORY_MAPPING = TunableMapping(
        description=
        '\n        A mapping from a notebook category ID to its shared category tuning \n        data.\n        ',
        key_type=TunableEnumEntry(
            description='\n            Category type.\n            ',
            tunable_type=NotebookCategories,
            default=NotebookCategories.INVALID,
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            Global data associated to a notebook category.\n            ',
            category_name=TunableLocalizedString(
                description=
                '\n                Name corresponding a the notebook category.\n                '
            ),
            category_description=OptionalTunable(
                description=
                '\n                Description corresponding to the notebook category.\n                ',
                tunable=TunableLocalizedString()),
            category_icon=TunableIconAllPacks(
                description=
                '\n                Icon to display on the notebook UI corresponding to a category.\n                ',
                allow_none=True),
            subcategories=TunableMapping(
                description=
                '\n                A mapping from a notebook category ID to its global tuning data.\n                ',
                key_type=TunableEnumEntry(
                    description=
                    '\n                    Subcategory type.\n                    ',
                    tunable_type=NotebookSubCategories,
                    default=NotebookSubCategories.INVALID,
                    pack_safe=True),
                value_type=TunableTuple(
                    description=
                    '\n                    Mapping of subcategory ID to the shared subcategory data.\n                    ',
                    subcategory_name=TunableLocalizedString(
                        description=
                        '\n                        Name corresponding to a notebook subcategory.\n                        '
                    ),
                    subcategory_icon=TunableIconAllPacks(
                        description=
                        '\n                        Icon to display on the notebook UI corresponding to a \n                        subcategory.\n                        ',
                        allow_none=True),
                    subcategory_tooltip=TunableLocalizedString(
                        description=
                        '\n                        Tooltip to be displayed when a player mouses over a\n                        subcategory icon.\n                        ',
                        allow_none=True),
                    is_sortable=OptionalTunable(
                        description=
                        '\n                        If enabled, entries of subcategory will be presented \n                        sorted alphabetically.\n                        ',
                        tunable=TunableTuple(include_new_entry=Tunable(
                            description=
                            '\n                                If checked, the entries that has new entry\n                                tag will be sorted.\n                                ',
                            tunable_type=bool,
                            default=False))),
                    format_type=TunableEnumEntry(
                        description=
                        '\n                        Type of entry this notification will look like no the UI.\n                        - Expandable data corresponds to rows of data that expands into\n                          subitems.  For example: Scientist serums will have an expandable\n                          option to display the ingredients for the serums.\n                        - Numbered data corresponds to a list of items to be numbered\n                          as they become available.  For example: Detective notes get \n                          displayed a a numbered list.\n                        - Icon description data corresponds at an entry of an icon with\n                          some text describing it.  For example detective evidence.\n                        ',
                        tunable_type=NotebookEntryType,
                        default=NotebookEntryType.EXPANDABLE_DATA),
                    show_max_entries=OptionalTunable(
                        Tunable(
                            description=
                            '\n                        If this is tuned, UI will use this value to display\n                        the amount of missing entries for a subcategory.\n                        For example if we tune this value to 3 and we \n                        unlock a notebook entry UI will display the data\n                        for the one entry that was unlocked but will display\n                        an empty UI field showing the player its missing \n                        two more.\n                        ',
                            tunable_type=int,
                            default=1)),
                    entry_list_texts=TunableTuple(
                        description=
                        '\n                        Text that will be shown in entry list.\n                        ',
                        has_list_text=OptionalTunable(
                            description=
                            '\n                            Text that will be shown when entry has list.\n                            ',
                            tunable=TunableLocalizedString()),
                        no_list_text=OptionalTunable(
                            description=
                            '\n                            Text that will be shown when entry has no list.\n                            ',
                            tunable=TunableLocalizedString()))))))

    @classmethod
    def get_category_id(cls, subcategory_id):
        for (key, value) in cls.NOTEBOOK_CATEGORY_MAPPING.items():
            for subcat_id in value.subcategories:
                if subcat_id == subcategory_id:
                    return key
Ejemplo n.º 10
0
class SpellbookCategoryData(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'content_list': TunableTuple(description='\n            Tuning used for the content list.\n            ', icon=TunableIconAllPacks(description='\n                Icon used to display this category in the content list.\n                '), tooltip=OptionalTunable(description='\n                Tooltip used in the spellbook for this category.\n                If unset, no tooltip is shown.\n                ', tunable=TunableLocalizedString())), 'front_page': TunableTuple(description='\n            Tuning used for the first page of the category.\n            ', category_description=OptionalTunable(description='\n                Description used in the spellbook.\n                If unset, description is not shown.\n                ', tunable=TunableLocalizedString()), icon=TunableIconAllPacks(description='\n                Icon used to display this category in first page.\n                ')), 'page': TunableTuple(description='\n            Tuning used for pages other than the front page of the category.\n            ', icon=OptionalTunable(description='\n                Icon shown on each page of this category.\n                ', tunable=TunableIconAllPacks())), 'tab': TunableTuple(description='\n            Tuning used to display the category on the tabs at the\n            top of the book.\n            ', icon=TunableIconAllPacks(description='\n                Icon used to display the category on a tab.\n                '), tooltip=OptionalTunable(description='\n                Tooltip used in the spellbook on the the tab for this category.\n                If unset, Category Name is used.\n                ', tunable=TunableLocalizedString())), 'category_name': TunableLocalizedString(description='Name of this category'), 'content': TunableVariant(spells=TunableTuple(entries=TunableList(description='\n                    List of spells in this category.\n                    ', tunable=TunableReference(description='The spell.', manager=services.get_instance_manager(Types.SPELL), pack_safe=True)), category_type=TunableEnumEntry(description='\n                    The category this corresponds to.\n                    ', tunable_type=BookCategoryDisplayType, default=BookCategoryDisplayType.WITCH_PRACTICAL_SPELL, invalid_enums=(BookCategoryDisplayType.WITCH_POTION,))), potions=TunableTuple(entries=TunableList(description='\n                    List of potions in this category.\n                    ', tunable=TunableReference(description="The potion's recipe.", manager=services.get_instance_manager(Types.RECIPE), class_restrictions=('Recipe',), pack_safe=True)), locked_args={'category_type': BookCategoryDisplayType.WITCH_POTION}), default='spells')}
Ejemplo n.º 11
0
class SpellbookTuning:
    FRONT_PAGE_DATA = TunableTuple(description='\n        UI-specific data used to display front page.\n        ', title=TunableLocalizedStringFactory(description='\n            The title to use on the front page of the spellbook.\n            '), icon=OptionalTunable(description='\n            Image displayed on front page of spellbook.\n            If unset, image is not shown.\n            ', tunable=TunableIconAllPacks()), page_description=OptionalTunable(description='\n            Description used for this page in the spellbook.\n            If unset, description is not shown.\n            ', tunable=TunableLocalizedString()))
    CATEGORY_LIST_DATA = TunableTuple(description='\n        UI-specific data used to display second page of the spellbook.\n        ', title=TunableLocalizedStringFactory(description='\n            The title to use on the category list of the spellbook.\n            '), icon=OptionalTunable(description='\n            Icon used on the category list page of the spellbook.\n            ', tunable=TunableIconAllPacks()), page_description=OptionalTunable(description='\n            Description used for this page in the spellbook.\n            If unset, description is not shown.\n            ', tunable=TunableLocalizedString()))
    CATEGORY_DATAS = TunableList(description='\n        A list of a spellbook category data.\n        ', tunable=SpellbookCategoryData.TunableFactory(), tuning_group=GroupNames.UI)
    POTION_DISPLAY_DATA = TunableMapping(description="\n        A mapping of a potion's recipe to it's spellbook display data. \n        ", key_type=TunableReference(description="\n            The potion's recipe.\n            ", manager=services.get_instance_manager(Types.RECIPE), class_restrictions=('Recipe',), pack_safe=True), value_type=SpellbookRecipeData.TunableFactory(), tuning_group=GroupNames.UI)
    INGREDIENTS_LABEL = TunableLocalizedString(description='\n        Text used to display ingredients label for a spell or potion.\n        \n        e.g. "Ingredients:"\n        ', tuning_group=GroupNames.UI)
    PROGRESS_LABEL = TunableLocalizedString(description='\n        Text used to display field name for progress into a specific\n        category.\n        \n        e.g. "Learned:"\n        ', tuning_group=GroupNames.UI)
    PROGRESS_TEXT_FORMAT = TunableLocalizedStringFactory(description='\n        Text used to display the progress towards completing a specific\n        category.  Takes current items learned and and total available.\n\n        e.g. "{0.Number}/{1.Number}"\n        ', tuning_group=GroupNames.UI)
    CATEGORY_FRONT_PAGE_ENTRY_COUNT = TunableRange(description=',\n        Number of entries allotted for the front page of a category section.\n        ', tunable_type=int, minimum=0, tuning_group=GroupNames.UI, default=2)
    CATEGORY_ENTRY_COUNT = TunableRange(description=',\n        Number of entries allotted for the subsequent pages of a category section.\n        ', tunable_type=int, minimum=1, tuning_group=GroupNames.UI, default=4)
    INGREDIENT_FORMAT = TunableLocalizedStringFactory(description='\n        The format used for ingredients in the spellbook.\n        First parameter will be name of ingredient, second will be quantity required.\n        e.g. {0.String}({1.Number}) = "Frog(1)"\n        ', tuning_group=GroupNames.UI)
Ejemplo n.º 12
0
class SpellbookRecipeData(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'potion_description': OptionalTunable(description='\n            Description used in the spellbook.\n            If unset, uses the recipe description.\n            ', tunable=TunableLocalizedString()), 'locked_description': OptionalTunable(description='\n            Description used in the spellbook if potion is not yet unlocked.\n            If unset, uses potion_description.\n            ', tunable=TunableLocalizedString()), 'icon': TunableIconAllPacks(description='\n            Icon used to display this recipe in the spellbook.\n            '), 'tooltip': OptionalTunable(description='\n            Tooltip used in the spellbook.\n            If unset, no tooltip is shown.\n            ', tunable=TunableLocalizedString())}