class DisplaySnippetPickerSuperInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'picker_dialog':
        TunablePickerDialogVariant(
            description='\n            The item picker dialog.\n            ',
            available_picker_flags=ObjectPickerTuningFlags.ITEM,
            tuning_group=GroupNames.PICKERTUNING),
        'subject':
        TunableEnumFlags(
            description=
            "\n            To whom 'loot on selected' should be applied.\n            ",
            enum_type=ParticipantTypeSim,
            default=ParticipantTypeSim.Actor,
            tuning_group=GroupNames.PICKERTUNING),
        'display_snippets':
        TunableList(
            description=
            '\n            The list of display snippets available to select and paired loot actions\n            that will run if selected.\n            ',
            tunable=_PickerDisplaySnippet.TunableFactory(
                description=
                '\n                Display snippet available to select.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'display_snippet_text_tokens':
        LocalizationTokens.TunableFactory(
            description=
            '\n            Localization tokens passed into the display snippet text fields.\n            \n            When acting on the individual items within the snippet list, the \n            following text tokens will be appended to this list of tokens (in \n            order):\n            0: snippet instance display name\n            1: snippet instance display description\n            2: snippet instance display tooltip\n            3: tokens tuned alongside individual snippets within the snippet list\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'display_snippet_text_overrides':
        OptionalTunable(
            description=
            '\n            If enabled, display snippet text overrides for all snippets \n            to be displayed in the picker. \n            \n            Can be used together with the display snippet text tokens to \n            act as text wrappers around the existing snippet display data.\n            ',
            tunable=_DisplaySnippetTextOverrides.TunableFactory(
                description=
                '\n                Display snippet text overrides for all snippets to be displayed\n                in the picker. \n            \n                Can be used together with the display snippet text tokens to \n                act as text wrappers around the existing snippet display data.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'continuations':
        TunableList(
            description=
            '\n            List of continuations to push when a snippet is selected.\n            \n            ID of the snippet will be the PickedItemID participant in the \n            continuation.\n            ',
            tunable=TunableContinuation(),
            tuning_group=GroupNames.PICKERTUNING),
        'run_continuations_on_no_selection':
        Tunable(
            description=
            '\n            Checked, runs continuations regardless if anything is selected.\n            Unchecked, continuations are only run if something is selected.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.PICKERTUNING)
    }

    @classmethod
    def has_valid_choice(cls, target, context, **kwargs):
        snippet_count = 0
        for _ in cls.picker_rows_gen(target, context, **kwargs):
            snippet_count += 1
            if snippet_count >= cls.picker_dialog.min_selectable:
                return True
        return False

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

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        target = target if target is not DEFAULT else inst.target
        context = context if context is not DEFAULT else inst.context
        resolver = InteractionResolver(cls,
                                       inst,
                                       target=target,
                                       context=context)
        general_tokens = inst_or_cls.display_snippet_text_tokens.get_tokens(
            resolver)
        overrides = inst_or_cls.display_snippet_text_overrides
        index = 0
        for display_snippet_data in inst_or_cls.display_snippets:
            display_snippet = display_snippet_data.display_snippet
            resolver = InteractionResolver(
                cls,
                inst,
                target=target,
                context=context,
                picked_item_ids={display_snippet.guid64})
            test_result = display_snippet_data.test(resolver)
            is_enable = test_result.result
            if is_enable or test_result.tooltip is not None:
                snippet_default_tokens = (
                    display_snippet.display_name(*general_tokens)
                    if display_snippet.display_name is not None else None,
                    display_snippet.display_description(*general_tokens) if
                    display_snippet.display_description is not None else None,
                    display_snippet.display_tooltip(*general_tokens)
                    if display_snippet.display_tooltip is not None else None)
                snippet_additional_tokens = display_snippet_data.display_snippet_text_tokens.get_tokens(
                    resolver)
                tokens = general_tokens + snippet_default_tokens + snippet_additional_tokens
                display_snippet = overrides(
                    display_snippet_data.display_snippet)
                tooltip = None if not overrides is not None or test_result.tooltip is None else lambda *_, tooltip=test_result.tooltip: tooltip(
                    *tokens)
                tooltip = None if display_snippet.display_tooltip is None else lambda *_, tooltip=display_snippet.display_tooltip: tooltip(
                    *tokens)
                row = BasePickerRow(
                    is_enable=is_enable,
                    name=display_snippet.display_name(*tokens),
                    icon=display_snippet.display_icon,
                    tag=index,
                    row_description=display_snippet.display_description(
                        *tokens),
                    row_tooltip=tooltip)
                yield row
            index += 1

    def _on_display_snippet_selected(self, picked_choice, **kwargs):
        resolver = self.get_resolver(**kwargs)
        for loot_on_selected in self.display_snippets[
                picked_choice].loot_on_selected:
            loot_on_selected.apply_to_resolver(resolver)

    def on_choice_selected(self, picked_choice, **kwargs):
        if picked_choice is None:
            if self.run_continuations_on_no_selection:
                for continuation in self.continuations:
                    self.push_tunable_continuation(continuation)
            return
        display_snippet = self.display_snippets[picked_choice].display_snippet
        picked_item_set = {display_snippet.guid64}
        self._on_display_snippet_selected(picked_choice,
                                          picked_item_ids=picked_item_set)
        for continuation in self.continuations:
            self.push_tunable_continuation(continuation,
                                           picked_item_ids=picked_item_set)
예제 #2
0
class UiTuning:
    LOADING_SCREEN_STRINGS = TunableMapping(
        description=
        '\n        Mapping from the Pack to its associated loading strings.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack containing the strings.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_type=TunableList(
            description=
            '\n            The list of loading screen strings which belongs to the pack.\n            We always display the strings from base game AND from the latest\n            pack which the player is entitled to and has installed. \n            ',
            tunable=TunableLocalizedString()),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='LoadingScreenStringsTuple')
    GO_HOME_INTERACTION = TunableReference(
        description=
        '\n        The interaction to push a Sim to go home.\n        ',
        manager=services.affordance_manager(),
        export_modes=(ExportModes.ClientBinary, ))
    COME_NEAR_ACTIVE_SIM = TunableReference(
        description=
        '\n        An affordance to push on a Sim so they come near the active Sim.\n        ',
        manager=services.affordance_manager())
    BRING_HERE_INTERACTION = TunableReference(
        description=
        '\n        An affordance to push on household members to summon them to the\n        current lot if they are not instanced.\n        ',
        manager=services.affordance_manager())
    NEW_CONTENT_ALERT_TUNING = TunableMapping(
        description=
        '\n        Mapping from Pack to its associated new content alert tuning\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack containing the new content tuning. NOTE: this should never\n            be tuned to BASE_GAME. That would trigger for all users.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_type=TunableTuple(
            description=
            '\n            Each pack will have a set of tuning of images and text to display\n            to inform the user what new features have been introduced in the \n            pack.\n            ',
            export_class_name='TunablePackContentTuple',
            title=TunableLocalizedString(
                description=
                '\n                The title to be displayed at the top of the New Content Alert\n                UI for this pack.\n                '
            ),
            cycle_images=TunableList(
                description=
                '\n                A list of images (screenshots) that the UI cycles through to\n                show off some of the new features.\n                ',
                tunable=TunableResourceKey(
                    resource_types=sims4.resources.CompoundTypes.IMAGE)),
            feature_list=TunableList(
                description=
                '\n                A list of tuples that describe each new feature in the New\n                Content Alert UI. NOTE: This should NEVER have more than 4\n                elements in it.\n                ',
                maxlength=4,
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains title text, description, an icon,\n                    and a reference to the matching lesson for this new \n                    feature.\n                    ',
                    export_class_name='TunableFeatureTuple',
                    title_text=TunableLocalizedString(
                        description=
                        '\n                        A title to be displayed in bold for the feature.\n                        '
                    ),
                    description_text=TunableLocalizedString(
                        description=
                        '\n                        A short description of the new feature.\n                        '
                    ),
                    icon=TunableResourceKey(
                        description=
                        '\n                        An icon that represents the feature.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    lesson=TunableReference(
                        description=
                        '\n                        A reference to the lesson that the user can go look at\n                        for this new feature.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.TUTORIAL),
                        allow_none=True,
                        pack_safe=True)))),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='NewContentAlertTuple')
    PACK_SPECIFIC_DATA = TunableMapping(
        description=
        '\n        Mapping from a Pack to its associated data.  This includes pack icons,\n        filter strings, and the credits file.\n        ',
        key_name='packId',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack id for the associated data.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_name='packData',
        value_type=TunableTuple(
            description=
            '\n            Each pack will have a set icons and can have an optional filter \n            string for use in Build/CAS and an optional Credits Title\n            ',
            export_class_name='TunablePackDataTuple',
            credits_title=TunableLocalizedString(
                description=
                '\n                The title used in the credits dropdown to select this packs credits.\n                If set, there must be a creditsxml file for this pack\n                in Assets/InGame/UI/Flash/data/\n                ',
                allow_none=True),
            filter_name=TunableLocalizedString(
                description=
                '\n                The name to used to describe the pack in CAS and BuildBuy filters.\n                If set, this pack will appear in the filter list.\n                ',
                allow_none=True),
            pack_type=TunableEnumEntry(
                description=
                '\n                Which type of pack is this.\n                ',
                tunable_type=PackTypes,
                default=PackTypes.BASE),
            icon_32=TunableResourceKey(
                description=
                '\n                Pack icon. 32x32.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_64=TunableResourceKey(
                description=
                '\n                Pack icon. 64x64.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_128=TunableResourceKey(
                description=
                '\n                Pack icon. 128x128.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_owned=TunableIcon(
                description=
                '\n                Pack icon that is displayed in the main menu\n                pack display when the player owns that pack.\n                ',
                allow_none=True),
            icon_unowned=TunableIcon(
                description=
                '\n                Pack icon that is displayed in the main menu\n                pack display when the player does not own that pack.\n                ',
                allow_none=True),
            webstore_id=Tunable(
                description=
                '\n                web store pack specific url identifier\n\t\t\t\t',
                tunable_type=str,
                default=None),
            region_list=TunableList(
                description=
                '\n                A list of tuples that describe each new region in the pack.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains metadata for a world select region.\n                    ',
                    export_class_name='TunablePackRegionTuple',
                    region_resource=TunableRegionDescription(
                        description=
                        '\n                            Reference to the region description catalog resource associated with this region\n                        ',
                        pack_safe=True),
                    is_player_facing=Tunable(
                        description=
                        '\n                            Whether to display this region in world select when the user does not own the associated pack\n                        ',
                        tunable_type=bool,
                        default=False),
                    region_name=TunableLocalizedString(
                        description=
                        '\n                        Localized name of region.\n                        ',
                        allow_none=True),
                    region_description=TunableLocalizedString(
                        description=
                        '\n                        Localized description of region.\n                        ',
                        allow_none=True),
                    overlay_layer=TunableResourceKey(
                        description=
                        '\n                        Hero image displayed on mouse over of region in\n\t\t\t\t\t\tworld selection UI.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE,
                        allow_none=True),
                    parallax_layers=TunableList(
                        description=
                        '\n                        Images used for scrolling parallax layers for region\n                        in world selection UI. Max number of images = 5.\n                        ',
                        maxlength=5,
                        tunable=TunableResourceKey(
                            resource_types=sims4.resources.CompoundTypes.IMAGE
                        )),
                    is_destination_region=Tunable(
                        description=
                        '\n                        Whether this region is a destination world.\n                        ',
                        tunable_type=bool,
                        default=False))),
            promo_cycle_images=TunableList(
                description=
                '\n                A list of promo screenshots and titles to display in the \n                Pack Detail panel.\n                ',
                tunable=PromoCycleImagesTuning(
                    description=
                    '\n                    Screenshots and label displayed in the Pack Detail Panel\n                    and Pack Preview Panel.\n                    '
                )),
            short_description=TunableLocalizedString(
                description=
                '\n                Short description of the pack meant to be displayed in \n                a tooltip.\n                ',
                allow_none=True)),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='PackSpecificDataTuple')
    BUNDLE_SPECIFIC_DATA = TunableMapping(
        description=
        '\n        Mapping from an MTX Bundle to its associated data. This is for bundles that\n        should appear in the ui, but are not packs. This includes main menu icons,\n        description, and the action associated with that bundle.\n        ',
        key_type=TunableMTXBundle(
            description=
            '\n            The MTX bundle id for the associated data.\n            ',
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            Each bundle has icons and a description, as well as an\n            data for the action performed when the bundle is interacted \n            with either the PromotionDialog or the PackDisplayPanel.\n            ',
            bundle_name=TunableLocalizedString(
                description=
                '\n                Name used in pack detail panel and main menu. If empty,\n                we fall back to using the MTX product name.\n                ',
                allow_none=True),
            icon_owned=TunableIcon(
                description=
                '\n                Bundle icon that is displayed in the main menu\n                pack display when the player is entitled to that bundle.\n                '
            ),
            icon_unowned=TunableIcon(
                description=
                '\n                Bundle icon that is displayed in the main menu\n                pack display when the player is not entitled to that bundle.\n                '
            ),
            short_description=TunableLocalizedString(
                description=
                '\n                Short description of the bundle meant to be displayed in \n                a tooltip.\n                '
            ),
            action=TunableVariant(
                description=
                '\n                The action that should be performed when this bundle is interacted with\n                in either the PromotionDialog or the PackDisplayPanel.\n                ',
                url=Tunable(
                    description=
                    '\n                    External url to open from PackDisplayPanel.\n                    ',
                    tunable_type=str,
                    default=None),
                promo_data=TunableTuple(
                    description=
                    '\n                    Data that populates PromotionDialog.\n                    ',
                    title=TunableLocalizedString(
                        description=
                        '\n                        Title of the promotion.\n                        '
                    ),
                    text=TunableLocalizedString(
                        description=
                        '\n                        Text describing the promotion.\n                        '
                    ),
                    image=TunableIcon(
                        description=
                        '\n                        Image displayed in the promotion dialog.\n                        '
                    ),
                    legal_text=TunableLocalizedString(
                        description=
                        '\n                        Legal text required for this promotion.\n                        ',
                        allow_none=True),
                    export_class_name='TunablePromoDataTuple'),
                default='url'),
            export_class_name='TunableBundleDataTuple'),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='BundleSpecificDataTuple')
    PACK_RELEASE_ORDER = TunableList(
        description='\n        List of Pack Ids in release order.\n        ',
        tunable=TunableEnumEntry(
            description='\n            A pack Id.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        export_modes=(ExportModes.ClientBinary, ))
    CHALLENGE_DATA = TunableList(
        description=
        '\n        List of challenge event data for engagement challenge notification UI.\n        ',
        tunable=TunableTuple(
            description=
            '\n            Data for each engagement challenge event.\n            ',
            export_class_name='TunableChallengeNotificationTuple',
            challenge_list=TunableList(
                description=
                '\n                A list of tuples that describe each challenge.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains data for a challenge.\n                    ',
                    export_class_name='TunableChallengeDataTuple',
                    challenge_description=TunableLocalizedString(
                        description=
                        '\n                        The description of the challenge.\n                        ',
                        allow_none=True),
                    challenge_name=TunableLocalizedString(
                        description=
                        '\n                        The name of the challenge.\n                        ',
                        allow_none=True),
                    image=TunableResourceKey(
                        description=
                        '\n                        The main image displayed for challenge info.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    info_link=Tunable(
                        description=
                        '\n                        The url link to page for more info on a challenge.\n                        ',
                        tunable_type=str,
                        default='',
                        allow_empty=True),
                    event_display=TunableTuple(
                        description=
                        '\n                        Display data for a challenge event.\n                        ',
                        export_class_name='TunableChallengeEventDisplayTuple',
                        event_icon=TunableIcon(
                            description=
                            '\n                            An icon to use for the challenge event.\n                            ',
                            allow_none=True),
                        event_title=TunableLocalizedString(
                            description=
                            '\n                            Title to display.  If not provided, challenge name will be used.\n                            ',
                            allow_none=True),
                        end_time=TunableTuple(
                            description=
                            '\n                            Date and time (UTC) for when the challenge event is expected to end.\n                            This is currently used to compute the time remaining in the UI.\n                            ',
                            display_name='End Time (UTC)',
                            export_class_name='TunableChallengeDateTuple',
                            year=TunableRange(
                                description=
                                '\n                                Year\n                                ',
                                tunable_type=int,
                                default=2016,
                                minimum=2014),
                            month=TunableRange(
                                description=
                                '\n                                Month\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=1,
                                maximum=12),
                            day=TunableRange(
                                description=
                                '\n                                Day\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=1,
                                maximum=31),
                            hour=TunableRange(
                                description=
                                '\n                                Hour (24-hour)\n                                ',
                                tunable_type=int,
                                default=0,
                                minimum=0,
                                maximum=23),
                            minute=TunableRange(
                                description=
                                '\n                                Minute\n                                ',
                                tunable_type=int,
                                default=0,
                                minimum=0,
                                maximum=59)),
                        activity_icon=TunableIcon(
                            description=
                            '\n                            Icon to display beside the activity progress bar.\n                            ',
                            allow_none=True),
                        activity_progress_text=TunableLocalizedString(
                            description=
                            "\n                            Status text for when the player is still making progress towards\n                            the challenge goal.  This is currently displayed on a tooltip.\n                            A CSS class of 'timeremaining' will have its color changed\n                            when the event is close to ending.\n                            The following tokens are available:\n                            0 - Number: Current collection progress, if available.\n                            1 - Number: Collection goal, if available.\n                            2 - Number: Hours remaining.\n                            3 - Number: Days remaining.\n                            ",
                            allow_none=True),
                        activity_progress_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the progress text.\n                            ',
                            allow_none=True),
                        activity_complete_text=TunableLocalizedString(
                            description=
                            "\n                            Status text for when the player has met the challenge goal.\n                            This is currently displayed on a tooltip.\n                            If not specified, the in-progress text will be used.\n                            A CSS class of 'timeremaining' will have its color changed\n                            when the event is close to ending.\n                            The following tokens are available (same as the in-progress text):\n                            0 - Number: Current collection progress, if available.\n                            1 - Number: Collection goal, if available.\n                            2 - Number: Hours remaining.\n                            3 - Number: Days remaining.\n                            ",
                            allow_none=True),
                        activity_complete_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the challenge complete text.\n                            If not specified, the in-progress icon will be used.\n                            ',
                            allow_none=True),
                        community_progress_text=TunableLocalizedString(
                            description=
                            "\n                            Status text describing the community's progress.\n                            This is currently displayed on a tooltip.\n                            This text is displayed even when challenges do not have\n                            community goals.\n                            Two Number tokens are available:\n                            0 - Current community collection progress.\n                            1 - Community collection goal, if any.\n                            ",
                            allow_none=True),
                        community_progress_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the community status text.\n                            ',
                            allow_none=True),
                        community_complete_text=TunableLocalizedString(
                            description=
                            '\n                            Status text for when the community has met the challenge goal.\n                            This text is only used when a goal is defined.\n                            If not specified, the in-progress status text will be used.\n                            Two Number tokens are available:\n                            0 - Current community collection progress.\n                            1 - Community collection goal, if any.\n                            ',
                            allow_none=True),
                        community_complete_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the community challenge complete text.\n                            ',
                            allow_none=True),
                        community_goal_amount=Tunable(
                            description=
                            '\n                            Optional collection goal for the community to reach.\n                            ',
                            tunable_type=int,
                            default=0)),
                    collection_id=TunableEnumEntry(
                        description=
                        "\n                        A CollectionIdentifier that is associated with this\n                        challenge. This is used by the UI to tie a collectible \n                        with this challenge.\n                        \n                        Use the default of Unindentified for challenges that\n                        aren't associated with a particular collection.\n                        ",
                        tunable_type=CollectionIdentifier,
                        default=CollectionIdentifier.Unindentified,
                        export_modes=ExportModes.All),
                    reward_items=TunableList(
                        description=
                        '\n                        A list of tuples that describe rewards for challenge.\n                        ',
                        tunable=TunableTuple(
                            description=
                            '\n                            A tuple that contains data for a challenge reward item.\n                            ',
                            export_class_name='TunableChallengeRewardTuple',
                            reward_icon=TunableResourceKey(
                                description=
                                '\n                                The icon of reward item.\n                                ',
                                resource_types=sims4.resources.CompoundTypes.
                                IMAGE),
                            reward_name=TunableLocalizedString(
                                description=
                                '\n                                The name of reward item.\n                                ',
                                allow_none=True))))),
            challenge_subtitle=TunableLocalizedString(
                description=
                '\n                The subtitle text to be displayed in notification UI.\n                ',
                allow_none=True),
            challenge_title=TunableLocalizedString(
                description=
                '\n                The title text to be displayed in notification UI.\n                ',
                allow_none=True),
            switch_name=Tunable(
                description=
                '\n                Server switch name to check whether challenge is active.\n                ',
                tunable_type=str,
                default='',
                allow_empty=True)),
        export_modes=(ExportModes.ClientBinary, ))
    PLATFORM_STRING_REPLACEMENTS = TunableList(
        description=
        '\n        A list of strings that will be swapped out when in use on different \n        platforms. Each entry contains the original and replacement LocKey, the platforms\n        to perform the swap on, and the input method that is in use when the\n        LocKey is used.\n        ',
        tunable=TunableTuple(
            original_string=TunableLocalizedString(
                description=
                '\n                The string that will be replaced or ignored.\n                '
            ),
            replacement_string=OptionalTunable(
                description=
                '\n                The string that will be used in place of original_string. If\n                omitted, original_string will simply be ignored entirely.\n                ',
                tunable=TunableLocalizedString()),
            platform=TunableEnumEntry(
                description=
                '\n                The platforms on which the string will be replaced.\n                ',
                tunable_type=Platform,
                default=Platform.CONSOLE),
            input_method=TunableEnumEntry(
                description=
                '\n                The input method that should be in use when attempting to replace\n                the original_string.\n                ',
                tunable_type=InputMethod,
                default=InputMethod.ANY),
            export_modes=ExportModes.ClientBinary,
            export_class_name='PlatformStringReplacementTuple'))
    SCALING = TunableList(
        description=
        '\n        Defines a min/max ui scaling value for a screen resolution.\n        ',
        tunable=TunableTuple(
            screen_width=Tunable(
                description=
                '\n                Provide an integer value.\n                ',
                tunable_type=int,
                default=0),
            screen_height=Tunable(
                description=
                '\n                Provide an integer value.\n                ',
                tunable_type=int,
                default=0),
            scale_max=Tunable(
                description=
                '\n                Provide a float value.\n                ',
                tunable_type=float,
                default=1),
            scale_min=Tunable(
                description=
                '\n                Provide a float value.\n                ',
                tunable_type=float,
                default=1),
            export_modes=ExportModes.ClientBinary,
            export_class_name='UIScaleTuple'))
    CG_CHALLENGE_DATAS = TunableList(
        description="\n        A list of a challenge's data.\n        ",
        tunable=TunableTuple(
            cg_challenge_hashtag=TunableLocalizedString(
                description=
                '\n                Hashtag of this challenge\n                '
            ),
            cg_challenge_name=TunableLocalizedString(
                description=
                '\n                Name of this challenge\n                '),
            export_modes=ExportModes.ClientBinary,
            export_class_name='CGChallengeTuning'))
    DEFAULT_OVERLAY_MAP = TunableMapping(
        description=
        '\n        This is a mapping of MapOverlayEnum -> List of MapOverlayEnums. The key\n        is used as the layer to be shown when no other overlays are present.\n        The value is a list of overlay types that would result in the default\n        layer being turned off if both are active.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            This is the OverlayType that acts as the default for the grouping\n            of OverlayTypes.\n            ',
            tunable_type=MapOverlayEnum,
            default=MapOverlayEnum.NONE),
        value_type=TunableList(
            description=
            '\n            A list of OverlayTypes, that if turned on would result in the\n            default OverlayType being shut off.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The OverlayType that causes the default value to turn off.\n                ',
                tunable_type=MapOverlayEnum,
                default=MapOverlayEnum.NONE)),
        export_modes=ExportModes.All,
        tuple_name='OverlayDefaultData')
예제 #3
0
class Narrative(HasTunableReference,
                metaclass=HashedTunedInstanceMetaclass,
                manager=services.get_instance_manager(Types.NARRATIVE)):
    INSTANCE_TUNABLES = {
        'narrative_groups':
        TunableEnumSet(
            description=
            '\n            A set of narrative groups this narrative is a member of.\n            ',
            enum_type=NarrativeGroup,
            enum_default=NarrativeGroup.INVALID,
            invalid_enums=(NarrativeGroup.INVALID, )),
        'narrative_links':
        TunableMapping(
            description=
            '\n            A mapping of narrative event to the narrative that will trigger \n            when that narrative event triggers.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                Event of interest.\n                ',
                tunable_type=NarrativeEvent,
                default=NarrativeEvent.INVALID,
                invalid_enums=(NarrativeEvent.INVALID, )),
            value_type=TunableReference(
                description=
                '\n                The narrative the respective event transitions to while\n                this specific narrative is active. \n                ',
                manager=services.get_instance_manager(Types.NARRATIVE))),
        'additional_situation_shifts':
        TunableMapping(
            description=
            '\n            A mapping of situation shift type to the shift curve it provides.\n            ',
            key_type=TunableEnumEntry(
                description='\n                Shift type.\n                ',
                tunable_type=NarrativeSituationShiftType,
                default=NarrativeSituationShiftType.INVALID,
                invalid_enums=(NarrativeSituationShiftType.INVALID, )),
            value_type=SituationCurve.TunableFactory(
                description=
                '\n                The situation schedule this adds to the situation scheduler\n                if this shift type is opted into as an additional source.\n                ',
                get_create_params={'user_facing': False})),
        'situation_replacements':
        TunableMapping(
            description=
            '\n            A mapping of situation to a tuple of situation and tests to apply.\n            ',
            key_type=TunableReference(
                description=
                '\n                A situation that is available for situation replacement.\n                ',
                manager=services.get_instance_manager(Types.SITUATION)),
            value_type=TunableTuple(replacement=TunableReference(
                description=
                '\n                    A situation that is available for situation replacement.\n                    ',
                manager=services.get_instance_manager(Types.SITUATION)),
                                    replacement_tests=
                                    SituationReplacementTestList())),
        'environment_override':
        OptionalTunable(
            description=
            '\n            If tuned, this narrative can have some effect on world controls\n            such as skyboxes, ambient sounds, and vfx.\n            ',
            tunable=NarrativeEnvironmentOverride.TunableFactory()),
        'introduction':
        OptionalTunable(
            description=
            '\n            If enabled, an introduction dialog will be shown on the next zone\n            load (which could be a save/load, travel, switch to another\n            household, etc.) if the test passes.\n            ',
            tunable=TunableTuple(
                dialog=UiDialogOk.TunableFactory(
                    description=
                    '\n                    The dialog to show that introduces the narrative.\n                    '
                ),
                tests=TunableTestSet(
                    description=
                    '\n                    The test set that must pass for the introduction to be\n                    given. Only the global resolver is available.\n                    Sample use: Must be in a specific region.\n                    '
                ))),
        'dialog_on_activation':
        OptionalTunable(
            description=
            '\n            If enabled, an introduction dialog will be shown when the narrative\n            is activated, if the test passes.\n            ',
            tunable=TunableTuple(
                dialog=TunableUiDialogVariant(
                    description=
                    '\n                    The dialog to show when the narrative starts.\n                    '
                ),
                tests=TunableTestSet(
                    description=
                    '\n                    The test set that must pass for the dialog to be\n                    given. Only the global resolver is available.\n                    Sample use: Must be in a specific region.\n                    '
                ))),
        'audio_sting':
        OptionalTunable(
            description=
            '\n            If enabled, play the specified audio sting when this narrative starts.\n            ',
            tunable=TunablePlayAudio()),
        'sim_info_loots':
        OptionalTunable(
            description=
            '\n            Loots that will be given to all sim_infos when this narrative starts.\n            ',
            tunable=TunableTuple(
                loots=TunableList(tunable=TunableReference(
                    manager=services.get_instance_manager(
                        sims4.resources.Types.ACTION),
                    class_restrictions=('LootActions', ),
                    pack_safe=True)),
                save_lock_tooltip=TunableLocalizedString(
                    description=
                    '\n                    The tooltip/message to show on the save lock tooltip while\n                    the loots are processing.\n                    '
                ))),
        'narrative_threshold_links':
        TunableMapping(
            description=
            "\n            A mapping between the event listener to a narrative link\n            that will be activated if progress of that event type hits \n            the tuned threshold.          \n            \n            For example, if this narrative has the following narrative threshold\n            link:\n            \n            {\n            key type: GoldilocksListener\n            value_type:\n              Interval: -10, 10\n              below_link: TooCold_Goldilocks\n              above_link: TooHot_Goldilocks\n            }\n            \n            ... any Narrative Progression Loot tagged with the GoldilocksListener\n            event will increment this instance's narrative_progression_value. If\n            it ever goes above 10 or below -10, the corresponding narrative is\n            activated and this narrative will complete.\n            \n            NOTE: All active narratives' progression values begin at 0.   \n            ",
            key_type=TunableEnumEntry(
                description=
                '\n                The progression event that triggers the narrative transition\n                if a threshold is met.\n                ',
                tunable_type=NarrativeProgressionEvent,
                default=NarrativeProgressionEvent.INVALID,
                invalid_enums=(NarrativeProgressionEvent.INVALID, )),
            value_type=TunableTuple(
                interval=TunableInterval(
                    description=
                    '\n                    The interval defines the upper and lower bound of the\n                    narrative thresholds. If any of the thresholds are crossed,\n                    the corresponding narrative is activated.\n                    ',
                    tunable_type=int,
                    default_lower=-50,
                    default_upper=50),
                below_link=OptionalTunable(
                    description=
                    '\n                    The narrative that is activated if the lower threshold is\n                    passed.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            Types.NARRATIVE))),
                above_link=OptionalTunable(
                    description=
                    '\n                    The narrative that is activated if the upper threshold is\n                    passed.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            Types.NARRATIVE)))))
    }

    def __init__(self):
        self._introduction_shown = False
        self._should_suppress_travel_sting = False
        self._narrative_progression = {}
        for event in self.narrative_threshold_links.keys():
            self._narrative_progression[event] = 0

    def save(self, msg):
        msg.narrative_id = self.guid64
        msg.introduction_shown = self._introduction_shown
        for (event, progression) in self._narrative_progression.items():
            with ProtocolBufferRollback(
                    msg.narrative_progression_entries) as progression_msg:
                progression_msg.event = event
                progression_msg.progression = progression

    def load(self, msg):
        self._introduction_shown = msg.introduction_shown
        for narrative_progression_data in msg.narrative_progression_entries:
            self._narrative_progression[
                narrative_progression_data.
                event] = narrative_progression_data.progression

    def on_zone_load(self):
        self._should_suppress_travel_sting = False
        if self.introduction is not None:
            if not self._introduction_shown:
                resolver = GlobalResolver()
                if self.introduction.tests.run_tests(resolver):
                    dialog = self.introduction.dialog(None, resolver=resolver)
                    dialog.show_dialog()
                    self._introduction_shown = True
                    self._should_suppress_travel_sting = self.introduction.dialog.audio_sting is not None

    @property
    def should_suppress_travel_sting(self):
        return self._should_suppress_travel_sting

    def start(self):
        if self.dialog_on_activation is not None:
            resolver = GlobalResolver()
            if self.dialog_on_activation.tests.run_tests(resolver):
                dialog = self.dialog_on_activation.dialog(None,
                                                          resolver=resolver)
                dialog.show_dialog()
        if self.audio_sting is not None:
            play_tunable_audio(self.audio_sting)
        if self.sim_info_loots is not None:
            services.narrative_service().add_sliced_sim_info_loots(
                self.sim_info_loots.loots,
                self.sim_info_loots.save_lock_tooltip)

    def apply_progression_for_event(self, event, amount):
        if event not in self.narrative_threshold_links:
            return ()
        self._narrative_progression[event] += amount
        new_amount = self._narrative_progression[event]
        link_data = self.narrative_threshold_links[event]
        if new_amount in link_data.interval:
            return ()
        if new_amount < link_data.interval.lower_bound and link_data.below_link is not None:
            return (link_data.below_link, )
        elif link_data.above_link is not None:
            return (link_data.above_link, )
        return ()

    def get_progression_stat(self, event):
        return self._narrative_progression.get(event)
예제 #4
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')}
예제 #5
0
class BuffTransferOp(BaseTargetedLootOperation):
    __qualname__ = 'BuffTransferOp'
    FACTORY_TUNABLES = {
        'moods_only':
        Tunable(
            description=
            '\n            Checking this box will limit buff transfer between Actor to Target Sim to only mood\n            associated buffs.',
            tunable_type=bool,
            default=True),
        'buff_reason':
        OptionalTunable(
            description=
            '\n            If set, specify a reason why the buff was added.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                The reason the buff was added. This will be displayed in the\n                buff tooltip.\n                '
            )),
        'mood_types':
        OptionalTunable(
            TunableList(
                TunableReference(
                    description=
                    '\n            If enabled, only transfer buffs with associated moods in this list.\n            ',
                    manager=services.mood_manager()))),
        'polarity':
        OptionalTunable(
            TunableEnumEntry(
                description=
                '\n            If enabled, only transfer buffs that match the selected polarity.\n            ',
                tunable_type=BuffPolarity,
                default=BuffPolarity.NEUTRAL,
                needs_tuning=True,
                tuning_group=GroupNames.UI))
    }

    def __init__(self,
                 moods_only,
                 buff_reason,
                 mood_types=None,
                 polarity=None,
                 **kwargs):
        super().__init__(**kwargs)
        self._moods_only = moods_only
        self._buff_reason = buff_reason
        self._mood_types = mood_types
        self._polarity = polarity

    def _apply_to_subject_and_target(self, subject, target, resolver):
        old_buff_types = list(subject.get_active_buff_types())
        if self._moods_only:
            for buff_entry in old_buff_types:
                while buff_entry.mood_type is not None:
                    subject.remove_buff_by_type(buff_entry)
        else:
            for buff_entry in old_buff_types:
                subject.remove_buff_by_type(buff_entry)
        for target_buff in target.get_active_buff_types():
            if self._moods_only and target_buff.mood_type is None:
                pass
            if self._mood_types is not None and target_buff.mood_type not in self._mood_types:
                pass
            if self._polarity is not None and self._polarity is not target_buff.polarity:
                pass
            buff_commodity = target_buff.commodity
            subject.add_buff(target_buff)
            while buff_commodity is not None:
                tracker = subject.get_tracker(buff_commodity)
                tracker.set_max(buff_commodity)
                subject.set_buff_reason(target_buff, self._buff_reason)
예제 #6
0
class RepoSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'repo_person_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            The job and role state for the repo-person.\n            ',
            tuning_group=GroupNames.ROLES),
        'debtor_sim_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            The job and role state for the Sim from the active household whose\n            unpaid debt is being collected by the repo-person.\n            ',
            tuning_group=GroupNames.ROLES),
        'repo_amount':
        TunableTuple(
            description=
            '\n            Tuning that determines the simoleon amount the repo-person is\n            trying to collect.\n            ',
            target_amount=TunablePercent(
                description=
                '\n                The percentage of current debt which determines the base\n                amount the repo-person will try to collect.\n                ',
                default=10),
            min_and_max_collection_range=TunableInterval(
                description=
                '\n                Multipliers that define the range around the target amount\n                that determine which objects should be taken.\n                ',
                tunable_type=float,
                default_lower=1,
                default_upper=1),
            tuning_group=GroupNames.SITUATION),
        'save_lock_tooltip':
        TunableLocalizedString(
            description=
            '\n            The tooltip to show when the player tries to save the game while\n            this situation is running. The save is locked when the situation\n            starts.\n            ',
            tuning_group=GroupNames.SITUATION),
        'find_object_state':
        _FindObjectState.TunableFactory(
            description=
            '\n            The state that picks an object for the repo-person to take.\n            ',
            display_name='1. Find Object State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'nothing_to_take_state':
        _NothingToTakeState.TunableFactory(
            description=
            '\n            The state at which there is nothing for the repo-person to take.\n            ',
            display_name='2. Nothing To Take State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'idle_at_object_state':
        _IdleAtObjectState.TunableFactory(
            description=
            '\n            The state at which the repo-person waits near the picked object\n            and can be asked not to take the object.\n            ',
            display_name='3. Idle At Object State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'repossess_object_state':
        _RepossessObjectState.TunableFactory(
            description=
            '\n            The state at which the repo-person will repossess the picked object.\n            ',
            display_name='4. Repossess Object State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'leave_state':
        _LeaveState.TunableFactory(
            description=
            '\n            The state at which the repo-person leaves the lot.\n            ',
            display_name='5. Leave State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'valid_object_tests':
        TunableTestSet(
            description=
            '\n            Test set that determines if an object on the lot is valid for\n            repossession.\n            ',
            tuning_group=GroupNames.SITUATION),
        'ask_not_to_take_success_chances':
        TunableList(
            description=
            '\n            List of values that determine the chance of success of the ask\n            not to take interaction, with each chance being used once and then\n            moving to the next. After using all the tuned chances the next\n            ask not to take interaction will always fail.\n            ',
            tunable=SuccessChance.TunableFactory(
                description=
                '\n                Chance of success of the "Ask Not To Take" interaction.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'bribe_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            If this interaction completes successfully, the repo-person will\n            leave the lot without repossessing anything.\n            '
        ),
        'ask_not_to_take_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            When this interaction completes, the situation will determine if\n            the repo-person should find another object to repossess or not\n            based on the tuned success chances.\n            '
        ),
        'ask_not_to_take_failure_notification':
        OptionalTunable(
            description=
            '\n            A TNS that displays when an ask-not-to-take interaction fails, if enabled.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'ask_not_to_take_success_notification':
        OptionalTunable(
            description=
            '\n            A TNS that displays when an ask-not-to-take interaction succeeds, if enabled.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'debt_source':
        TunableEnumEntry(
            description=
            "\n            The source of where the debt is coming from and where it'll be removed.\n            ",
            tunable_type=DebtSource,
            default=DebtSource.SCHOOL_LOAN),
        'maximum_object_to_repossess':
        OptionalTunable(
            description=
            '\n            The total maximum objects that the situation will take.\n            ',
            tunable=TunableRange(
                description=
                '\n                The total maximum objects that the situation will take.\n                If Use Debt Amount is specified then the situation will keep taking objects\n                until there are no more valid objects to take or we have removed all of the\n                debt.\n                ',
                tunable_type=int,
                default=1,
                minimum=1),
            enabled_by_default=True,
            enabled_name='has_maximum_value',
            disabled_name='use_debt_amount'),
        'auto_clear_debt_event':
        OptionalTunable(
            description=
            '\n            If enabled then we will have an even we listen to to cancel the debt.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The event that when triggered will cause all the debt to be cancelled and the\n                repo man to leave.\n                ',
                tunable_type=TestEvent,
                default=TestEvent.Invalid,
                invalid_enums=(TestEvent.Invalid, )))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.objects_to_take = []
        self.current_object = None
        self.ask_not_to_take_success_chances_list = list(
            self.ask_not_to_take_success_chances)
        self._reservation_handler = None
        self._objects_repossessed = 0

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _WaitForRepoPersonState),
                SituationStateData(2,
                                   _FindObjectState,
                                   factory=cls.find_object_state),
                SituationStateData(3,
                                   _NothingToTakeState,
                                   factory=cls.nothing_to_take_state),
                SituationStateData(4,
                                   _IdleAtObjectState,
                                   factory=cls.idle_at_object_state),
                SituationStateData(5,
                                   _RepossessObjectState,
                                   factory=cls.repossess_object_state),
                SituationStateData(6, _LeaveState, factory=cls.leave_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.repo_person_job_and_role_state.job,
                 cls.repo_person_job_and_role_state.role_state),
                (cls.debtor_sim_job_and_role_state.job,
                 cls.debtor_sim_job_and_role_state.role_state)]

    @classmethod
    def default_job(cls):
        pass

    def repo_person(self):
        sim = next(
            self.all_sims_in_job_gen(self.repo_person_job_and_role_state.job),
            None)
        return sim

    def debtor_sim(self):
        sim = next(
            self.all_sims_in_job_gen(self.debtor_sim_job_and_role_state.job),
            None)
        return sim

    def _cache_valid_objects(self):
        debt_value = self.get_debt_value()
        if debt_value is None:
            self._self_destruct()
            return
        target_amount = debt_value * self.repo_amount.target_amount
        unsorted = []
        plex_service = services.get_plex_service()
        check_common_area = plex_service.is_active_zone_a_plex()
        debtor_household_id = self.debtor_sim().household_id
        for obj in services.object_manager().valid_objects():
            if not obj.get_household_owner_id() == debtor_household_id:
                continue
            if not obj.is_on_active_lot():
                continue
            if check_common_area and plex_service.get_plex_zone_at_position(
                    obj.position, obj.level) is None:
                continue
            if not obj.is_connected(self.repo_person()):
                continue
            if obj.children:
                continue
            resolver = SingleObjectResolver(obj)
            if self.valid_object_tests.run_tests(resolver):
                delta = abs(obj.depreciated_value - target_amount)
                unsorted.append((obj.id, delta))
        self.objects_to_take = sorted(unsorted, key=operator.itemgetter(1))

    def _on_add_sim_to_situation(self,
                                 sim,
                                 job_type,
                                 role_state_type_override=None):
        super()._on_add_sim_to_situation(
            sim, job_type, role_state_type_override=role_state_type_override)
        if self.debtor_sim() is not None and self.repo_person() is not None:
            self._cache_valid_objects()
            self._change_state(self.find_object_state())

    def _destroy(self):
        super()._destroy()
        self.clear_current_object()
        services.get_persistence_service().unlock_save(self)
        if self.auto_clear_debt_event is not None:
            services.get_event_manager().unregister_single_event(
                self, self.auto_clear_debt_event)

    def start_situation(self):
        services.get_persistence_service().lock_save(self)
        super().start_situation()
        self._change_state(_WaitForRepoPersonState())
        if self.auto_clear_debt_event is not None:
            services.get_event_manager().register_single_event(
                self, self.auto_clear_debt_event)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if self.auto_clear_debt_event is None:
            return
        if event != self.auto_clear_debt_event:
            return
        self.clear_debt()
        self._change_state(self.leave_state())

    def reduce_debt(self, amount):
        if self.debt_source == DebtSource.SCHOOL_LOAN:
            host_sim_info = services.sim_info_manager().get(
                self._guest_list.host_sim_id)
            statistic = host_sim_info.get_statistic(
                LoanTunables.DEBT_STATISTIC, add=False)
            if statistic is None:
                return
            else:
                statistic.add_value(-amount)
        elif self.debt_source == DebtSource.BILLS:
            services.active_household().bills_manager.reduce_amount_owed(
                amount)
        else:
            logger.error('Attempting to use a debt source that is not handled',
                         owner='jjacobson')
            return

    def clear_debt(self):
        if self.debt_source == DebtSource.SCHOOL_LOAN:
            host_sim_info = services.sim_info_manager().get(
                self._guest_list.host_sim_id)
            statistic = host_sim_info.get_statistic(
                LoanTunables.DEBT_STATISTIC, add=False)
            if statistic is None:
                return
            else:
                statistic.set_value(0)
        elif self.debt_source == DebtSource.BILLS:
            services.active_household().bills_manager.pay_bill(clear_bill=True)
        else:
            logger.error(
                'Attempting to use a debt source {} that is not handled',
                self.debt_source,
                owner='jjacobson')
            return

    def get_debt_value(self):
        if self.debt_source == DebtSource.SCHOOL_LOAN:
            host_sim_info = services.sim_info_manager().get(
                self._guest_list.host_sim_id)
            statistic = host_sim_info.get_statistic(
                LoanTunables.DEBT_STATISTIC, add=False)
            if statistic is None:
                return
            return statistic.get_value()
        if self.debt_source == DebtSource.BILLS:
            return services.active_household(
            ).bills_manager.current_payment_owed
        else:
            logger.error('Attempting to use a debt source that is not handled',
                         owner='jjacobson')
            return

    def on_object_repossessed(self):
        self._objects_repossessed += 1
        if self.maximum_object_to_repossess is None or self._objects_repossessed < self.maximum_object_to_repossess:
            debt_value = self.get_debt_value()
            if debt_value is not None and debt_value > 0:
                self._change_state(self.find_object_state())
                return
        self._change_state(self.leave_state())

    def get_target_object(self):
        return self.current_object

    def get_lock_save_reason(self):
        return self.save_lock_tooltip

    def set_current_object(self, obj):
        self.current_object = obj
        if self._reservation_handler is not None:
            logger.error(
                'Trying to reserve an object when an existing reservation already exists: {}',
                self._reservation_handler)
            self._reservation_handler.end_reservation()
        self._reservation_handler = self.current_object.get_reservation_handler(
            self.repo_person())
        self._reservation_handler.begin_reservation()

    def clear_current_object(self):
        self.current_object = None
        if self._reservation_handler is not None:
            self._reservation_handler.end_reservation()
            self._reservation_handler = None
예제 #7
0
class SecretLabZoneDirector(RegisterTestEventMixin,
                            SchedulingZoneDirectorMixin, ZoneDirectorBase):
    INSTANCE_TUNABLES = {
        'section_doors':
        TunableList(
            description=
            '\n            An ordered set of doors, each of which unlocks a section of the lab\n            to explore.\n            ',
            tunable=TunableReference(
                manager=services.get_instance_manager(Types.OBJECT)),
            unique_entries=True),
        'door_lock_operations':
        TunableTuple(
            description=
            '\n            These operations are applied to doors that should be locked\n            based on the progress into the zone.\n            ',
            object_state=ObjectStateValue.TunableReference(
                description=
                '\n                An object state that should be set on the door when locked.\n                '
            ),
            lock_data=LockDoor.TunableFactory(
                description=
                '\n                The LockDoor loot to run on the doors in the lab to lock them.\n                '
            )),
        'door_unlock_operations':
        TunableTuple(
            description=
            '\n            These operations are applied to doors that should be unlocked\n            based on the progress into the zone.\n            ',
            object_state=ObjectStateValue.TunableReference(
                description=
                '\n                An object state that should be set on the door when unlocked.\n                '
            ),
            lock_data=UnlockDoor.TunableFactory(
                description=
                '\n                The UnlockDoor loot to run on the doors when they should be unlocked.\n                '
            )),
        'reveal_interactions':
        TunableInteractionOfInterest(
            description=
            '\n            Interactions that, when run on a door, reveal the plex associated \n            to the interacted door.\n            '
        ),
        'object_commodities_to_fixup_on_load':
        TunableList(
            description=
            '\n            Normally object commodities retain their previously saved value on \n            load and do not simulate the decay up to the current time.\n            This list allows specific objects to update commodities based off\n            passage of time if time had elapsed between load and the last time\n            the zone was saved.\n            ',
            tunable=TunableTuple(
                commodity=TunableReference(
                    description=
                    '\n                    The commodity to fix up if time elapsed since zone was last saved.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        STATISTIC),
                    class_restrictions='Commodity'),
                object_test=TunableObjectMatchesDefinitionOrTagTest(
                    description=
                    '\n                    Test whether or not an object applies for this fixup.\n                    '
                )))
    }

    def __init__(self):
        super().__init__()
        self._revealed_plex = 0
        self._plex_door_map = self._generate_plex_door_map()
        self._reset_lab_data()
        self._command_handlers = {
            SecretLabCommand.RevealNextSection: self._reveal_next_section,
            SecretLabCommand.RevealAllSections: self._reveal_all_sections,
            SecretLabCommand.ResetLab: self._reset_all_sections
        }

    def on_startup(self):
        super().on_startup()
        self._register_test_event_for_keys(
            TestEvent.InteractionStart,
            self.reveal_interactions.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.reveal_interactions.custom_keys_gen())

    def on_shutdown(self):
        self._unregister_for_all_test_events()
        super().on_shutdown()

    def on_loading_screen_animation_finished(self):
        super().on_loading_screen_animation_finished()
        active_sim = services.get_active_sim()
        if active_sim is None:
            return
        if active_sim.is_on_active_lot():
            return
        camera.focus_on_sim(services.get_active_sim())

    def on_cleanup_zone_objects(self):
        super().on_cleanup_zone_objects()
        current_zone = services.current_zone()
        if current_zone.time_has_passed_in_world_since_zone_save():
            if self.object_commodities_to_fixup_on_load:
                time_of_last_zone_save = current_zone.time_of_last_save()
                for obj in list(services.object_manager().values()):
                    if not obj.is_on_active_lot():
                        continue
                    for commodity_fixup in self.object_commodities_to_fixup_on_load:
                        if not commodity_fixup.object_test(objects=(obj, )):
                            continue
                        fixup_commodity = obj.get_stat_instance(
                            commodity_fixup.commodity)
                        if fixup_commodity is not None:
                            fixup_commodity.update_commodity_to_time(
                                time_of_last_zone_save, update_callbacks=True)
        if self._should_reset_progress_on_load():
            self._revealed_plex = 0
        self._update_locks_and_visibility()

    def _determine_zone_saved_sim_op(self):
        if self._should_reset_progress_on_load():
            return _ZoneSavedSimOp.CLEAR
        return _ZoneSavedSimOp.MAINTAIN

    def _on_clear_zone_saved_sim(self, sim_info):
        if sim_info.is_selectable:
            self._request_spawning_of_sim_at_spawn_point(
                sim_info, SimSpawnReason.ACTIVE_HOUSEHOLD)
            return
        self._send_sim_home(sim_info)

    def _save_custom_zone_director(self, zone_director_proto, writer):
        writer.write_uint32(SAVE_LAST_REVEALED_PLEX, self._revealed_plex)
        super()._save_custom_zone_director(zone_director_proto, writer)

    def _load_custom_zone_director(self, zone_director_proto, reader):
        if reader is not None:
            self._revealed_plex = reader.read_uint32(SAVE_LAST_REVEALED_PLEX,
                                                     None)
        super()._load_custom_zone_director(zone_director_proto, reader)

    def handle_event(self, sim_info, event, resolver):
        interaction_start = event == TestEvent.InteractionStart
        interaction_complete = event == TestEvent.InteractionComplete
        if interaction_start or interaction_complete:
            if resolver(self.reveal_interactions):
                door = resolver.get_participant(ParticipantType.Object)
                try:
                    plex_to_unlock = self.section_doors.index(
                        door.definition) + 1
                except ValueError:
                    logger.error('Ran interaction {} on unexpected door {}',
                                 resolver.interaction, door)
                    plex_to_unlock = 0
                if interaction_complete:
                    self._handle_door_state(sim_info, door, True)
                else:
                    build_buy.set_plex_visibility(plex_to_unlock, True)
                    self._revealed_plex = max(plex_to_unlock,
                                              self._revealed_plex)

    def handle_command(self, command: SecretLabCommand, **kwargs):
        if command in self._command_handlers:
            self._command_handlers[command](**kwargs)

    def _should_reset_progress_on_load(self):
        current_zone = services.current_zone()
        return current_zone.active_household_changed_between_save_and_load(
        ) or current_zone.time_has_passed_in_world_since_zone_save()

    def _generate_plex_door_map(self):
        plex_door_map = {}
        obj_mgr = services.object_manager()
        for (i, door_def) in enumerate(self.section_doors, 1):
            door = next(iter(obj_mgr.get_objects_of_type_gen(door_def)), None)
            if door is None:
                logger.error(
                    'Unable to find the door {} on lot to unlock plex {}',
                    door_def, i)
            else:
                plex_door_map[i] = door
        return plex_door_map

    def _handle_door_state(self, sim_info, door, set_open):
        operations = self.door_unlock_operations if set_open else self.door_lock_operations
        resolver = SingleActorAndObjectResolver(sim_info, door, self)
        operations.lock_data.apply_to_resolver(resolver)
        state_value = operations.object_state
        door.set_state(state_value.state, state_value, force_update=True)

    def _update_locks_and_visibility(self):
        active_sim_info = services.active_sim_info()
        for i in range(1, len(self.section_doors) + 1):
            reveal = i <= self._revealed_plex
            build_buy.set_plex_visibility(i, reveal)
            door = self._plex_door_map.get(i, None)
            if door is None:
                continue
            self._handle_door_state(active_sim_info, door, reveal)

    def _reset_lab_data(self):
        self._revealed_plex = 0

    def _reveal_next_section(self):
        self._revealed_plex = min(len(self.section_doors),
                                  self._revealed_plex + 1)
        self._update_locks_and_visibility()

    def _reveal_all_sections(self):
        self._revealed_plex = len(self.section_doors)
        self._update_locks_and_visibility()

    def _reset_all_sections(self):
        self._reset_lab_data()
        self._update_locks_and_visibility()
예제 #8
0
class GlobalPolicy(DisplaySnippet):
    GLOBAL_POLICY_TOKEN_NON_ACTIVE = TunableLocalizedStringFactory(
        description=
        '\n        Display string that appears when trying to use a Global Policy Token\n        referencing a non-active Global Policy.\n        '
    )
    INSTANCE_TUNABLES = {
        'decay_days':
        TunableRange(
            description=
            '\n            The number of days it will take for the global policy to revert to\n            not-complete. Decay begins when the policy is completed.\n            ',
            tunable_type=int,
            default=5,
            minimum=0),
        'progress_initial_value':
        TunableRange(
            description=
            '\n            The initial value of global policy progress. Progress begins when\n            the policy is first set to in-progress.\n            ',
            tunable_type=int,
            default=0,
            minimum=0),
        'progress_max_value':
        TunableRange(
            description=
            '\n            The max value of global policy progress. Once the policy progress\n            reaches the max threshold, global policy state becomes complete.\n            ',
            tunable_type=int,
            default=100,
            minimum=1),
        'loot_on_decay':
        TunableList(
            description=
            '\n            A list of loot actions that will be run when the policy decays.\n            ',
            tunable=LootActions.TunableReference(
                description=
                '\n                The loot action will target the active Sim.\n                '
            )),
        'loot_on_complete':
        TunableList(
            description=
            '\n            A list of loot actions that will be run when the policy is complete.\n            ',
            tunable=LootActions.TunableReference(
                description=
                '\n                The loot action will target the active Sim.\n                '
            )),
        'global_policy_effects':
        TunableList(
            description=
            '\n            Actions to apply when the global policy is enacted.\n            ',
            tunable=GlobalPolicyEffectVariants(
                description=
                '\n                The action to apply.\n                '))
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.progress_max_value < cls.progress_initial_value:
            logger.error(
                'Global Policy {} has a max value less than the initial value. This is not allowed.',
                cls)

    def __init__(self, progress_initial_value=None, **kwargs):
        super().__init__(**kwargs)
        self._progress_state = GlobalPolicyProgressEnum.NOT_STARTED
        self._progress_value = 0
        self.decay_handler = None
        self.end_time_from_load = 0

    @property
    def progress_state(self):
        return self._progress_state

    @property
    def progress_value(self):
        return self._progress_value

    def pre_load(self, global_policy_data):
        self.set_progress_state(GlobalPolicyProgressEnum(
            global_policy_data.progress_state),
                                from_load=True)
        self.set_progress_value(global_policy_data.progress_value,
                                from_load=True)
        if global_policy_data.decay_days != 0:
            self.end_time_from_load = global_policy_data.decay_days

    def set_progress_state(self, progress_enum, from_load=False):
        old_state = self._progress_state
        self._progress_state = progress_enum
        if old_state != self._progress_state and not from_load:
            services.get_event_manager().process_event(
                TestEvent.GlobalPolicyProgress, custom_keys=(type(self), self))

    def set_progress_value(self, new_value, from_load=False):
        self._progress_value = new_value
        if not from_load:
            self._process_new_value(new_value)
        return self.progress_state

    def _process_new_value(self, new_value):
        if new_value <= self.progress_initial_value and self.progress_state != GlobalPolicyProgressEnum.NOT_STARTED:
            self.set_progress_state(GlobalPolicyProgressEnum.NOT_STARTED)
            self.decay_handler = None
            for effect in self.global_policy_effects:
                effect.turn_off(self.guid64)
        elif new_value >= self.progress_max_value and self.progress_state != GlobalPolicyProgressEnum.COMPLETE:
            self.set_progress_state(GlobalPolicyProgressEnum.COMPLETE)
            for effect in self.global_policy_effects:
                effect.turn_on(self.guid64)
        elif self.progress_state != GlobalPolicyProgressEnum.IN_PROGRESS:
            self.set_progress_state(GlobalPolicyProgressEnum.IN_PROGRESS)

    def apply_policy_loot_to_active_sim(self, loot_list, resolver=None):
        if resolver is None:
            resolver = SingleSimResolver(services.active_sim_info())
        for loot_action in loot_list:
            loot_action.apply_to_resolver(resolver)

    def decay_policy(self, timeline):
        yield timeline.run_child(SleepElement(TimeSpan.ZERO))
        services.global_policy_service().set_global_policy_progress(
            self, self.progress_initial_value)
        self.decay_handler = None
        self.apply_policy_loot_to_active_sim(self.loot_on_decay)

    @classmethod
    def get_non_active_display(cls, token_data):
        if token_data.token_property == GlobalPolicyTokenType.NAME:
            return LocalizationHelperTuning.get_raw_text(
                token_data.global_policy.display_name())
        if token_data.token_property == GlobalPolicyTokenType.PROGRESS:
            return LocalizationHelperTuning.get_raw_text(
                cls.GLOBAL_POLICY_TOKEN_NON_ACTIVE())
        logger.error(
            'Invalid Global Policy Property {} tuned on the Global Policy token.'
            .format(token_data.property))

    def get_active_policy_display(self, token_data):
        if token_data.token_property == GlobalPolicyTokenType.NAME:
            return LocalizationHelperTuning.get_raw_text(self.display_name())
        if token_data.token_property == GlobalPolicyTokenType.PROGRESS:
            progress_percentage_str = str(
                int(
                    round(
                        float(self.progress_value) /
                        float(self.progress_max_value), 2) * 100))
            return LocalizationHelperTuning.get_raw_text(
                progress_percentage_str)
        logger.error(
            'Invalid Global Policy Property {} tuned on the Global Policy token.'
            .format(token_data.property))
예제 #9
0
class InventoryStorage:
    UI_SORT_TYPES = TunableList(
        description=
        "\n        A list of gameplay-based sort types used in the sim's inventory in the UI.\n        ",
        tunable=TunableTuple(
            description=
            '\n            Data that defines this sort for the inventory UI.\n            ',
            sort_name=TunableLocalizedString(
                description=
                '\n                The name displayed in the UI for this sort type.\n                '
            ),
            object_data=TunableVariant(
                description=
                '\n                The object data that determines the sort order of\n                this sort type.\n                ',
                states=TunableList(
                    description=
                    '\n                    States whose values are used to sort on for this sort type. \n                    ',
                    tunable=TunableReference(
                        description=
                        '\n                        A State to sort on.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.OBJECT_STATE),
                        class_restrictions='ObjectState')),
                default='states'),
            is_ascending=Tunable(
                description=
                '\n                Whether a higher value from object_data will sort first.\n                If a high value means that the object should sort lower \n                (E.G. brokenness), this should be false.\n                ',
                tunable_type=bool,
                default=True),
            debug_name=Tunable(
                description=
                '\n                A unique name used to select this inventory sort type through \n                the console command ui.inventory.set_sort_filter when the inventory\n                ui is open.\n                ',
                tunable_type=str,
                default='NONE'),
            export_class_name='InventoryUISortTypeTuple',
            export_modes=ExportModes.ClientBinary))
    UI_FILTER_TYPES = TunableList(
        description=
        "\n        A list of filter categories containing filter types used to filter the sim's\n        inventory in the UI. The inventory can also be sorted by filter type; \n        filters lower on this list will sort lower when sorted by filter type.\n        ",
        tunable=TunableTuple(
            description=
            '\n            A category of filters in the UI. Contains a name and a list of filters.\n            ',
            filters=TunableList(
                description=
                '\n                The filters used in this category. \n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Data that defines a filter type in the inventory UI.\n                    ',
                    tags=TunableTags(
                        description=
                        '\n                        Tags that should be considered part of this filter.\n                        ',
                        binary_type=EnumBinaryExportType.EnumUint32),
                    filter_name=TunableLocalizedString(
                        description=
                        '\n                        The name displayed in the UI for this filter type.            \n                        '
                    ),
                    debug_name=Tunable(
                        description=
                        '\n                        A unique name used to select this inventory filter type through \n                        the console command ui.inventory.set_sort_filter when the inventory\n                        ui is open.\n                        ',
                        tunable_type=str,
                        default='NONE'),
                    export_class_name='InventoryUIFilterTypeTuple')),
            category_name=TunableLocalizedString(
                description=
                '\n                The name displayed in the UI for this filter category.\n                '
            ),
            export_class_name='InventoryUIFilterCategoryTuple',
            export_modes=ExportModes.ClientBinary))

    def __init__(self,
                 inventory_type,
                 item_location,
                 max_size=None,
                 allow_compaction=True,
                 allow_ui=True,
                 hidden_storage=False):
        self._objects = {}
        self._owners = WeakSet()
        self._inventory_type = inventory_type
        self._item_location = item_location
        self._max_size = max_size
        self._allow_compaction = allow_compaction
        self._allow_ui = allow_ui
        self._hidden_storage = hidden_storage
        self._stacks_with_options_counter = None

    def __len__(self):
        return len(self._objects)

    def __iter__(self):
        yield from iter(self._objects.values())

    def __contains__(self, obj_id):
        return obj_id in self._objects

    def __getitem__(self, obj_id):
        if obj_id in self._objects:
            return self._objects[obj_id]

    def __repr__(self):
        return 'InventoryStorage<{},{}>'.format(self._inventory_type,
                                                self._get_inventory_id())

    def register(self, owner):
        self._owners.add(owner)

    def unregister(self, owner):
        self._owners.discard(owner)

    def has_owners(self):
        if self._owners:
            return True
        return False

    def get_owners(self):
        return tuple(self._owners)

    @property
    def allow_ui(self):
        return self._allow_ui

    @allow_ui.setter
    def allow_ui(self, value):
        self._allow_ui = value

    def discard_object_id(self, obj_id):
        if obj_id in self._objects:
            del self._objects[obj_id]

    def discard_all_objects(self):
        for obj in self._objects.values():
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_REMOVE, obj)
            obj.inventoryitem_component.set_inventory_type(None, None)
        self._objects.clear()

    def can_insert(self, obj):
        if not obj.can_go_in_inventory_type(self._inventory_type):
            return False
        elif self._max_size is not None and sum(
                inventory_obj.stack_count()
                for inventory_obj in self) >= self._max_size:
            return False
        return True

    def insert(self, obj, inventory_object=None, compact=True):
        if not self.can_insert(obj):
            return False
        try:
            obj.on_before_added_to_inventory()
        except:
            logger.exception(
                'Exception invoking on_before_added_to_inventory. obj: {}',
                obj)
        self._insert(obj, inventory_object)
        try:
            obj.on_added_to_inventory()
        except:
            logger.exception(
                'Exception invoking on_added_to_inventory. obj: {}', obj)
        compacted_obj_id = None
        compacted_count = None
        if compact:
            (compacted_obj_id, compacted_count) = self._try_compact(obj)
        if compacted_obj_id is None:
            for owner in self._owners:
                try:
                    owner.on_object_inserted(obj)
                except:
                    logger.exception(
                        'Exception invoking on_object_inserted. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_ADD, obj)
            sent_stack_update = False
            if obj.inventoryitem_component.has_stack_option:
                if self._stacks_with_options_counter is None:
                    self._stacks_with_options_counter = defaultdict(int)
                stack_id = obj.inventoryitem_component.get_stack_id()
                stack_objects = self._stacks_with_options_counter[stack_id]
                if stack_objects == 0:
                    self._distribute_inventory_update_message(
                        UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION, obj)
                    sent_stack_update = True
                self._stacks_with_options_counter[stack_id] += 1
            if not sent_stack_update:
                obj_owner = obj.inventoryitem_component.get_inventory().owner
                if obj_owner.is_sim and obj_owner.sim_info.favorites_tracker is not None and obj_owner.sim_info.favorites_tracker.is_favorite_stack(
                        obj):
                    self._distribute_inventory_update_message(
                        UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION, obj)
        else:
            for owner in self._owners:
                try:
                    owner.on_object_id_changed(obj, compacted_obj_id,
                                               compacted_count)
                except:
                    logger.exception(
                        'Exception invoking on_object_id_changed. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_UPDATE,
                obj,
                obj_id=compacted_obj_id)
        return True

    def update_object_stack_by_id(self, obj_id, new_stack_id):
        if obj_id not in self._objects:
            return
        obj = self._objects[obj_id]
        self._distribute_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_REMOVE, obj)
        obj.set_stack_id(new_stack_id)
        self._distribute_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_ADD, obj)

    def remove(self, obj, count=1, move_to_object_manager=True):
        if obj.id not in self._objects:
            return False
        old_stack_count = obj.stack_count()
        split_obj = self._try_split(obj, count)
        try:
            obj.on_before_removed_from_inventory()
        except:
            logger.exception(
                'Exception invoking on_before_removed_from_inventory. obj: {}',
                obj)
        self._remove(obj, move_to_object_manager=move_to_object_manager)
        try:
            obj.on_removed_from_inventory()
        except:
            logger.exception(
                'Exception invoking on_removed_from_inventory. obj: {}', obj)
        if split_obj is None:
            for owner in self._owners:
                try:
                    owner.on_object_removed(obj)
                except:
                    logger.exception(
                        'Exception invoking on_object_removed. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_REMOVE, obj)
            if obj.inventoryitem_component.has_stack_option and self._stacks_with_options_counter is not None:
                stack_id = obj.inventoryitem_component.get_stack_id()
                self._stacks_with_options_counter[stack_id] -= 1
                if stack_id in self._stacks_with_options_counter <= 0:
                    if self._stacks_with_options_counter[stack_id] < 0:
                        logger.error(
                            'Counter went negative for stack_id {} with scheme {}',
                            stack_id,
                            obj.inventoryitem_component.stack_scheme,
                            owner='jdimailig')
                    del self._stacks_with_options_counter[stack_id]
        else:
            for owner in self._owners:
                try:
                    owner.on_object_id_changed(split_obj, obj.id,
                                               old_stack_count)
                except:
                    logger.exception(
                        'Exception invoking on_object_id_changed. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_UPDATE,
                split_obj,
                obj_id=obj.id)
        return True

    def _insert(self, obj, inventory_object):
        self._objects[obj.id] = obj
        obj.inventoryitem_component.set_inventory_type(self._inventory_type,
                                                       inventory_object)
        obj.item_location = self._item_location
        if self._inventory_type == InventoryType.SIM:
            obj.inventoryitem_component.is_hidden = self._hidden_storage
        object_manager = services.object_manager()
        if obj.id in object_manager:
            object_manager.move_to_inventory(
                obj,
                services.current_zone().inventory_manager)
            obj.set_parent(None)
            posture_graph_service = services.current_zone(
            ).posture_graph_service
            if posture_graph_service.is_object_pending_deletion(obj):
                posture_graph_service.finalize_object_deletion(obj)

    def _remove(self, obj, move_to_object_manager=False):
        if move_to_object_manager:
            services.current_zone().inventory_manager.move_to_world(
                obj, services.object_manager())
        obj.item_location = ItemLocation.ON_LOT
        obj.inventoryitem_component.set_inventory_type(
            None, None, from_removal=not move_to_object_manager)
        del self._objects[obj.id]

    def _get_compact_data(self, obj):
        try:
            obj.inventoryitem_component.save_for_stack_compaction = True
            return obj.get_attribute_save_data()
        finally:
            obj.inventoryitem_component.save_for_stack_compaction = False
            obj.post_tooltip_save_data_stored()

    def _try_compact(self, obj):
        if not self._allow_compaction:
            return (None, None)
        if len(self._objects) < 2:
            return (None, None)
        if obj.has_component(
                components.types.OBJECT_CLAIM_COMPONENT
        ) and obj.object_claim_component.requires_claiming:
            return (None, None)
        similar = None
        def_id = obj.definition.id
        data = self._get_compact_data(obj)
        stack_id = obj.inventoryitem_component.get_stack_id()
        for other in self._objects.values():
            if def_id != other.definition.id:
                continue
            if other is obj:
                continue
            if stack_id != other.inventoryitem_component.get_stack_id():
                continue
            if not any(interaction.should_reset_based_on_pipeline_progress
                       for interaction in other.interaction_refs):
                other_data = self._get_compact_data(other)
                if data == other_data:
                    similar = other
                    break
        if similar is None:
            return (None, None)
        similar_id = similar.id
        similar_count = similar.stack_count()
        self._remove(similar)
        similar.destroy(source=self, cause='InventoryStorage compaction')
        obj.update_stack_count(similar_count)
        return (similar_id, similar_count)

    def _try_split(self, obj, count):
        if count >= obj.stack_count():
            return
        clone = obj.inventoryitem_component.get_clone_for_stack_split()
        self._insert(clone, obj.inventoryitem_component.last_inventory_owner)
        clone.update_stack_count(-count)
        obj.set_stack_count(count)
        clone.on_added_to_inventory()
        return clone

    def _get_inventory_id(self):
        if InventoryTypeTuning.is_shared_between_objects(self._inventory_type):
            return int(self._inventory_type)
        if self._owners:
            return next(iter(self._owners)).owner.id
        logger.error(
            "Non-shared storage that's missing an owner: InventoryStorage<{},{}>",
            self._inventory_type, 0)
        return 0

    def _get_inventory_ui_type(self):
        if InventoryTypeTuning.is_shared_between_objects(self._inventory_type):
            return UI_pb2.InventoryItemUpdate.TYPE_SHARED
        return UI_pb2.InventoryItemUpdate.TYPE_OBJECT

    def _get_inventory_update_message(self,
                                      update_type,
                                      obj,
                                      obj_id=None,
                                      allow_while_zone_not_running=False):
        if not self._allow_ui:
            return
        if not services.current_zone(
        ).is_zone_running and not allow_while_zone_not_running:
            return
        if services.current_zone().is_zone_shutting_down:
            return
        msg = UI_pb2.InventoryItemUpdate()
        msg.type = update_type
        msg.inventory_id = self._get_inventory_id()
        msg.inventory_type = self._get_inventory_ui_type()
        msg.stack_id = obj.inventoryitem_component.get_stack_id()
        if obj_id is None:
            msg.object_id = obj.id
        else:
            msg.object_id = obj_id
        if update_type == UI_pb2.InventoryItemUpdate.TYPE_ADD:
            add_data = UI_pb2.InventoryItemData()
            add_data.definition_id = obj.definition.id
            msg.add_data = add_data
        if update_type == UI_pb2.InventoryItemUpdate.TYPE_ADD or update_type == UI_pb2.InventoryItemUpdate.TYPE_UPDATE:
            dynamic_data = UI_pb2.DynamicInventoryItemData()
            dynamic_data.value = obj.current_value
            dynamic_data.count = obj.stack_count()
            dynamic_data.new_object_id = obj.id
            dynamic_data.is_new = obj.new_in_inventory
            dynamic_data.sort_order = obj.get_stack_sort_order()
            icon_info = obj.get_icon_info_data()
            build_icon_info_msg(icon_info, None, dynamic_data.icon_info)
            recipe_name = obj.get_tooltip_field(
                TooltipFieldsComplete.recipe_name
            ) or obj.get_craftable_property(GameObjectProperty.RECIPE_NAME)
            if recipe_name is not None:
                dynamic_data.recipe_name = recipe_name
            if obj.custom_name is not None:
                dynamic_data.custom_name = obj.custom_name
            if InventoryStorage.UI_SORT_TYPES:
                sort_type = 0
                for sort_type_data in InventoryStorage.UI_SORT_TYPES:
                    value = None
                    try:
                        abs_value = None
                        state_component = obj.state_component
                        if state_component is None:
                            continue
                        for state in sort_type_data.object_data:
                            if state_component.has_state(state):
                                test_value = float(
                                    state_component.get_state(state).value)
                                abs_test_value = abs(test_value)
                                if value is None:
                                    value = test_value
                                elif abs_value < abs_test_value:
                                    value = test_value
                                    abs_value = abs_test_value
                    except TypeError:
                        pass
                    if value is not None:
                        sort_data_item = UI_pb2.InventoryItemSortData()
                        sort_data_item.type = sort_type
                        sort_data_item.value = value
                        dynamic_data.sort_data.append(sort_data_item)
                    sort_type += 1
            if update_type == UI_pb2.InventoryItemUpdate.TYPE_ADD:
                msg.add_data.dynamic_data = dynamic_data
            else:
                msg.update_data = dynamic_data
        if update_type == UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION:
            dynamic_data = UI_pb2.DynamicInventoryItemData()
            if obj.inventoryitem_component.has_stack_option:
                obj.inventoryitem_component.populate_stack_icon_info_data(
                    dynamic_data.icon_info)
            obj_owner = obj.inventoryitem_component.get_inventory().owner
            if obj_owner.is_sim:
                favorites_tracker = obj_owner.sim_info.favorites_tracker
                if favorites_tracker is not None:
                    if favorites_tracker.is_favorite_stack(obj):
                        dynamic_data.is_favorite = True
            msg.update_data = dynamic_data
        return msg

    def _distribute_inventory_update_message(self,
                                             update_type,
                                             obj,
                                             obj_id=None):
        msg = self._get_inventory_update_message(update_type,
                                                 obj,
                                                 obj_id=obj_id)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op_with_no_owner(op)

    def distribute_inventory_update_message(self, obj):
        if obj.id not in self._objects:
            return False
        msg = self._get_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_UPDATE, obj)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op_with_no_owner(op)

    def distribute_inventory_stack_update_message(self, obj):
        if obj.id not in self._objects:
            return
        msg = self._get_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION, obj)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op_with_no_owner(op)

    def distribute_owned_inventory_update_message(self, obj, owner):
        if obj.id not in self._objects:
            return False
        msg = self._get_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_UPDATE, obj)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op(owner, op)

    def get_item_update_ops_gen(self):
        stack_options_set = set()
        for obj in self._objects.values():
            message = self._get_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_ADD,
                obj,
                allow_while_zone_not_running=True)
            if message is None:
                continue
            yield (obj,
                   GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE,
                                           message))
            if not obj.inventoryitem_component.has_stack_option:
                obj_owner = obj.inventoryitem_component.get_inventory().owner
                if obj_owner.is_sim:
                    if obj_owner.sim_info.favorites_tracker is None:
                        continue
                    stack_id = obj.inventoryitem_component.get_stack_id()
                    if stack_id in stack_options_set:
                        continue
                    option_msg = self._get_inventory_update_message(
                        UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION,
                        obj,
                        allow_while_zone_not_running=True)
                    if option_msg is not None:
                        stack_options_set.add(stack_id)
                        yield (obj,
                               GenericProtocolBufferOp(
                                   Operation.INVENTORY_ITEM_UPDATE,
                                   option_msg))
            else:
                stack_id = obj.inventoryitem_component.get_stack_id()
                if stack_id in stack_options_set:
                    continue
                option_msg = self._get_inventory_update_message(
                    UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION,
                    obj,
                    allow_while_zone_not_running=True)
                if option_msg is not None:
                    stack_options_set.add(stack_id)
                    yield (obj,
                           GenericProtocolBufferOp(
                               Operation.INVENTORY_ITEM_UPDATE, option_msg))

    def open_ui_panel(self, obj):
        if not self._allow_ui:
            return False
        msg = UI_pb2.OpenInventory()
        msg.object_id = obj.id
        msg.inventory_id = self._get_inventory_id()
        msg.inventory_type = self._get_inventory_ui_type()
        op = GenericProtocolBufferOp(Operation.OPEN_INVENTORY, msg)
        Distributor.instance().add_op_with_no_owner(op)
        return True
예제 #10
0
 def __init__(
         self,
         description='Holds information about carrying and putting down an object.',
         **kwargs):
     super().__init__(
         put_down_tuning=TunableVariant(reference=TunableReference(
             description=
             '\n                    Tuning for how to score where a Sim might want to set an\n                    object down.\n                    ',
             manager=services.get_instance_manager(
                 sims4.resources.Types.STRATEGY)),
                                        literal=TunablePutDownStrategy().
                                        TunableFactory(),
                                        default='literal'),
         state_based_put_down_tuning=TunableMapping(
             description=
             '\n                A mapping from a state value to a putdownstrategy. If the\n                owning object is in any of the states tuned here, it will use\n                that state\'s associated putdownstrategy in place of the one\n                putdownstrategy tuned in the "put_down_tuning" field. If the\n                object is in multiple states listed in this mapping, the\n                behavior is undefined.\n                ',
             key_type=TunableReference(
                 description=
                 '\n                    The state value this object must be in in order to use the\n                    associated putdownstrategy.\n                    ',
                 manager=services.get_instance_manager(
                     sims4.resources.Types.OBJECT_STATE)),
             value_type=TunableVariant(reference=TunableReference(
                 description=
                 '\n                        Tuning for how to score where a Sim might want to set\n                        an object down.\n                        ',
                 manager=services.get_instance_manager(
                     sims4.resources.Types.STRATEGY)),
                                       literal=TunablePutDownStrategy().
                                       TunableFactory()),
             key_name='State',
             value_name='PutDownStrategy'),
         carry_affordances=OptionalTunable(TunableList(
             TunableReference(
                 description=
                 '\n                    The versions of the HoldObject affordance that this object\n                    supports.\n                    ',
                 manager=services.affordance_manager())),
                                           disabled_name=
                                           'use_default_affordances',
                                           enabled_name=
                                           'use_custom_affordances'),
         provided_affordances=TunableList(
             description=
             '\n                A list of affordances that are generated when a Sim holding\n                this object selects another Sim to interact with. The generated\n                interactions will target the selected Sim but will have this\n                object set as their carry target.\n                ',
             tunable=TunableReference(
                 manager=services.affordance_manager())),
         constraint_pick_up=OptionalTunable(
             description=
             '\n                A list of constraints that must be fulfilled in order to\n                interact with this object.\n                ',
             tunable=TunableList(
                 tunable=interactions.constraints.TunableConstraintVariant(
                     description=
                     '\n                        A constraint that must be fulfilled in order to\n                        interact with this object.\n                        '
                 ))),
         allowed_hands=TunableVariant(locked_args={
             'both': (Hand.LEFT, Hand.RIGHT),
             'left_only': (Hand.LEFT, ),
             'right_only': (Hand.RIGHT, )
         },
                                      default='both'),
         holster_while_routing=Tunable(
             description=
             '\n                If True, the Sim will holster the object before routing and\n                unholster when the route is complete.\n                ',
             tunable_type=bool,
             default=False),
         holster_compatibility=TunableAffordanceFilterSnippet(
             description=
             '\n                Define interactions for which holstering this object is\n                explicitly disallowed.\n                \n                e.g. The Scythe is tuned to be holster-incompatible with\n                sitting, meaning that Sims will holster the Sctyhe when sitting.\n                '
         ),
         unholster_on_long_route_only=Tunable(
             description=
             '\n                If True, then the Sim will not unholster this object (assuming\n                it was previously holstered) unless a transition involving a\n                long route is about to happen.\n                \n                If False, then the standard holstering rules apply.\n                ',
             tunable_type=bool,
             default=False),
         prefer_owning_sim_inventory_when_not_on_home_lot=Tunable(
             description=
             "\n                If checked, this object will highly prefer to be put into the\n                owning Sim's inventory when being put down by the owning Sim on\n                a lot other than their home lot.\n                \n                Certain objects, like consumables, should be exempt from this.\n                ",
             tunable_type=bool,
             default=True),
         description=description,
         **kwargs)
예제 #11
0
class Venue(metaclass=HashedTunedInstanceMetaclass,
            manager=services.get_instance_manager(sims4.resources.Types.VENUE)
            ):
    __qualname__ = 'Venue'
    INSTANCE_TUNABLES = {
        'display_name':
        TunableLocalizedString(
            description=
            '\n            Name that will be displayed for the venue\n            ',
            export_modes=ExportModes.All),
        'display_name_incomplete':
        TunableLocalizedString(
            description=
            '\n            Name that will be displayed for the incomplete venue\n            ',
            export_modes=ExportModes.All),
        'venue_description':
        TunableLocalizedString(
            description='Description of Venue that will be displayed',
            export_modes=ExportModes.All),
        'venue_icon':
        TunableResourceKey(None,
                           resource_types=sims4.resources.CompoundTypes.IMAGE,
                           description='Venue Icon for UI',
                           export_modes=ExportModes.All),
        'venue_thumbnail':
        TunableResourceKey(None,
                           resource_types=sims4.resources.CompoundTypes.IMAGE,
                           description='Image of Venue that will be displayed',
                           export_modes=ExportModes.All),
        'allow_game_triggered_events':
        Tunable(
            description=
            '\n            Whether this venue can have game triggered events. ex for careers\n            ',
            tunable_type=bool,
            default=False),
        'background_event_schedule':
        TunableSituationWeeklyScheduleFactory(
            description=
            '\n            The Background Events that run on this venue. They run underneath\n            any user facing Situations and there can only be one at a time. The\n            schedule times and durations are windows in which background events\n            can start.\n            '
        ),
        'special_event_schedule':
        TunableSituationWeeklyScheduleFactory(
            description=
            '\n            The Special Events that run on this venue. These run on top of\n            Background Events. We run only one user facing event at a time, so\n            if the player started something then this may run in the\n            background, otherwise the player will be invited to join in on this\n            Venue Special Event.\n            '
        ),
        'required_objects':
        TunableList(
            description=
            '\n            A list of objects that are required to be on a lot before\n            that lot can be labeled as this venue.\n            ',
            tunable=TunableVenueObject(
                description=
                "\n                    Specify object tag(s) that must be on this venue.\n                    Allows you to group objects, i.e. weight bench,\n                    treadmill, and basketball goals are tagged as\n                    'exercise objects.'\n                    \n                    This is not the same as automatic objects tuning. \n                    Please read comments for both the fields.\n                    "
            ),
            export_modes=ExportModes.All),
        'npc_summoning_behavior':
        sims4.tuning.tunable.TunableMapping(
            description=
            '\n            Whenever an NPC is summoned to a lot by the player, determine\n            which action to take based on the summoning purpose. The purpose\n            is a dynamic enum: venues.venue_constants.NPCSummoningPurpose.\n            \n            The action will generally involve either adding a sim to an existing\n            situation or creating a situation then adding them to it.\n            \n            \\depot\\Sims4Projects\\Docs\\Design\\Open Streets\\Open Street Invite Matrix.xlsx\n            \n            residential: This is behavior pushed on the NPC if this venue was a residential lot.\n            create_situation: Place the NPC in the specified situation/job pair.\n            add_to_background_situation: Add the NPC the currently running background \n            situation in the venue.\n            ',
            key_type=sims4.tuning.tunable.TunableEnumEntry(
                venues.venue_constants.NPCSummoningPurpose,
                venues.venue_constants.NPCSummoningPurpose.DEFAULT),
            value_type=TunableVariant(
                locked_args={'disabled': None},
                residential=ResidentialLotArrivalBehavior.TunableFactory(),
                create_situation=CreateAndAddToSituation.TunableFactory(),
                add_to_background_situation=AddToBackgroundSituation.
                TunableFactory(),
                default='disabled'),
            tuning_group=GroupNames.TRIGGERS),
        'player_requires_visitation_rights':
        OptionalTunable(
            description=
            'If enabled, then lots of this venue type  \n            will require player Sims that are not on their home lot to go through \n            the process of being greeted before they are\n            given full rights to using the lot.\n            ',
            tunable=TunableTuple(
                ungreeted=Situation.TunableReference(
                    description=
                    '\n                    The situation to create for ungreeted player sims on this lot.',
                    display_name='Player Ungreeted Situation'),
                greeted=Situation
                .TunableReference(
                    description=
                    '\n                    The situation to create for greeted player sims on this lot.',
                    display_name='Player Greeted Situation'))),
        'zone_fixup':
        TunableVariant(
            description=
            '\n            Specify what to do with a non resident NPC\n            when the zone has to be fixed up on load. \n            This fix up will occur if sim time or the\n            active household has changed since the zone was last saved.\n            ',
            residential=ResidentialZoneFixupForNPC.TunableFactory(),
            create_situation=CreateAndAddToSituation.TunableFactory(),
            add_to_background_situation=AddToBackgroundSituation.
            TunableFactory(),
            default='residential',
            tuning_group=GroupNames.SPECIAL_CASES),
        'travel_interaction_name':
        TunableVariant(
            description=
            '\n            Specify what name a travel interaction gets when this Venue is an\n            adjacent lot.\n            ',
            visit_residential=ResidentialTravelDisplayName.TunableFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                residence.\n                '
            ),
            visit_venue=TunableLocalizedStringFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                commercial venue.\n                Tokens: 0:ActorSim\n                Example: "Visit The Bar"\n                '
            ),
            tuning_group=GroupNames.SPECIAL_CASES),
        'travel_with_interaction_name':
        TunableVariant(
            description=
            '\n            Specify what name a travel interaction gets when this Venue is an\n            adjacent lot.\n            ',
            visit_residential=ResidentialTravelDisplayName.TunableFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                residence and the actor Sim is traveling with someone.\n                '
            ),
            visit_venue=TunableLocalizedStringFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                commercial venue and the actor is traveling with someone.\n                Tokens: 0:ActorSim\n                Example: "Visit The Bar With..."\n                '
            ),
            tuning_group=GroupNames.SPECIAL_CASES),
        'venue_requires_front_door':
        Tunable(
            description=
            '\n            True if this venue should run the front door generation code. \n            If it runs, venue will have the ring doorbell interaction and \n            its additional behavior.\n            ',
            tunable_type=bool,
            default=False),
        'automatic_objects':
        TunableList(
            description=
            '\n            A list of objects that is required to exist on this venue (e.g. the\n            mailbox). If any of these objects are missing from this venue, they\n            will be auto-placed on zone load.',
            tunable=TunableTuple(
                description=
                "\n                An item that is required to be present on this venue. The object's tag \n                will be used to determine if any similar objects are present. If no \n                similar objects are present, then the object's actual definition is used to \n                create an object of this type.\n                \n                This is not the same as required objects tuning. Please read comments \n                for both the fields.\n                \n                E.g. To require a mailbox to be present on a lot, tune a hypothetical basicMailbox \n                here. The code will not trigger as long as a basicMailbox, fancyMailbox, or \n                cheapMailbox are present on the lot. If none of them are, then a basicMailbox \n                will be automatically created.\n                ",
                default_value=TunableReference(
                    manager=services.definition_manager(),
                    description=
                    'The default object to use if no suitably tagged object is present on the lot.'
                ),
                tag=TunableEnumEntry(description='The tag to search for',
                                     tunable_type=tag.Tag,
                                     default=tag.Tag.INVALID))),
        'hide_from_buildbuy_ui':
        Tunable(
            description=
            '\n            If True, this venue type will not be available in the venue picker\n            in build/buy.\n            ',
            tunable_type=bool,
            default=False,
            export_modes=ExportModes.All),
        'allows_fire':
        Tunable(
            description=
            '\n            If True a fire can happen on this venue, \n            otherwise fires will not spawn on this venue.\n            ',
            tunable_type=bool,
            default=False),
        'allow_rolestate_routing_on_navmesh':
        Tunable(
            description=
            '\n            Allow all RoleStates routing permission on lot navmeshes of this\n            venue type. This is particularly useful for outdoor venue types\n            (lots with no walls), where it is awkward to have to "invite a sim\n            in" before they may route on the lot, be called over, etc.\n            \n            This tunable overrides the "Allow Npc Routing On Active Lot"\n            tunable of individual RoleStates.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.special_event_schedule is not None:
            for entry in cls.special_event_schedule.schedule_entries:
                while entry.situation.venue_situation_player_job is None:
                    logger.error(
                        'Venue Situation Player Job {} tuned in Situation: {}',
                        entry.situation.venue_situation_player_job,
                        entry.situation)

    def __init__(self, **kwargs):
        self._active_background_event_id = None
        self._active_special_event_id = None
        self._background_event_schedule = None
        self._special_event_schedule = None

    def set_active_event_ids(self,
                             background_event_id=None,
                             special_event_id=None):
        self._active_background_event_id = background_event_id
        self._active_special_event_id = special_event_id

    @property
    def active_background_event_id(self):
        return self._active_background_event_id

    @property
    def active_special_event_id(self):
        return self._active_special_event_id

    def schedule_background_events(self, schedule_immediate=True):
        self._background_event_schedule = self.background_event_schedule(
            start_callback=self._start_background_event,
            schedule_immediate=False)
        if schedule_immediate:
            (
                best_time_span, best_data_list
            ) = self._background_event_schedule.time_until_next_scheduled_event(
                services.time_service().sim_now, schedule_immediate=True)
            if best_time_span is not None and best_time_span == date_and_time.TimeSpan.ZERO:
                while True:
                    for best_data in best_data_list:
                        self._start_background_event(
                            self._background_event_schedule, best_data)

    def schedule_special_events(self, schedule_immediate=True):
        self._special_event_schedule = self.special_event_schedule(
            start_callback=self._try_start_special_event,
            schedule_immediate=schedule_immediate)

    def _start_background_event(self, scheduler, alarm_data, extra_data=None):
        entry = alarm_data.entry
        situation = entry.situation
        situation_manager = services.get_zone_situation_manager()
        if self._active_background_event_id is not None and self._active_background_event_id in situation_manager:
            situation_manager.destroy_situation_by_id(
                self._active_background_event_id)
        situation_id = services.get_zone_situation_manager().create_situation(
            situation, user_facing=False, spawn_sims_during_zone_spin_up=True)
        self._active_background_event_id = situation_id

    def _try_start_special_event(self, scheduler, alarm_data, extra_data):
        entry = alarm_data.entry
        situation = entry.situation
        situation_manager = services.get_zone_situation_manager()
        if self._active_special_event_id is None:
            client_manager = services.client_manager()
            client = next(iter(client_manager.values()))
            invited_sim = client.active_sim
            active_sim_available = situation.is_situation_available(
                invited_sim)

            def _start_special_event(dialog):
                guest_list = None
                if dialog.accepted:
                    start_user_facing = True
                    guest_list = SituationGuestList()
                    guest_info = SituationGuestInfo.construct_from_purpose(
                        invited_sim.id, situation.venue_situation_player_job,
                        SituationInvitationPurpose.INVITED)
                    guest_list.add_guest_info(guest_info)
                else:
                    start_user_facing = False
                situation_id = situation_manager.create_situation(
                    situation,
                    guest_list=guest_list,
                    user_facing=start_user_facing)
                self._active_special_event_id = situation_id

            if not situation_manager.is_user_facing_situation_running(
            ) and active_sim_available:
                dialog = situation.venue_invitation_message(
                    invited_sim, SingleSimResolver(invited_sim))
                dialog.show_dialog(
                    on_response=_start_special_event,
                    additional_tokens=(
                        situation.display_name,
                        situation.venue_situation_player_job.display_name))
            else:
                situation_id = situation_manager.create_situation(
                    situation, user_facing=False)
                self._active_special_event_id = situation_id

    def shut_down(self):
        if self._background_event_schedule is not None:
            self._background_event_schedule.destroy()
        if self._special_event_schedule is not None:
            self._special_event_schedule.destroy()
        situation_manager = services.get_zone_situation_manager()
        if self._active_background_event_id is not None:
            situation_manager.destroy_situation_by_id(
                self._active_background_event_id)
            self._active_background_event_id = None
        if self._active_special_event_id is not None:
            situation_manager.destroy_situation_by_id(
                self._active_special_event_id)
            self._active_special_event_id = None

    @classmethod
    def lot_has_required_venue_objects(cls, lot):
        failure_reasons = []
        for required_object_tuning in cls.required_objects:
            object_test = required_object_tuning.object
            object_list = object_test()
            num_objects = len(object_list)
            while num_objects < required_object_tuning.number:
                pass
        failure_message = None
        failure = len(failure_reasons) > 0
        if failure:
            failure_message = ''
            for message in failure_reasons:
                failure_message += message + '\n'
        return (not failure, failure_message)

    def summon_npcs(self, npc_infos, purpose, host_sim_info=None):
        if self.npc_summoning_behavior is None:
            return
        summon_behavior = self.npc_summoning_behavior.get(purpose)
        if summon_behavior is None:
            summon_behavior = self.npc_summoning_behavior.get(
                venues.venue_constants.NPCSummoningPurpose.DEFAULT)
            if summon_behavior is None:
                return
        summon_behavior(npc_infos, host_sim_info)

    @classproperty
    def requires_visitation_rights(cls):
        return cls.player_requires_visitation_rights is not None

    @classproperty
    def player_ungreeted_situation_type(cls):
        if cls.player_requires_visitation_rights is None:
            return
        return cls.player_requires_visitation_rights.ungreeted

    @classproperty
    def player_greeted_situation_type(cls):
        if cls.player_requires_visitation_rights is None:
            return
        return cls.player_requires_visitation_rights.greeted
예제 #12
0
class CarryableComponent(
        objects.components.Component,
        component_name=objects.components.types.CARRYABLE_COMPONENT):
    __qualname__ = 'CarryableComponent'
    DEFAULT_CARRY_AFFORDANCES = TunableList(
        TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION)),
        description='A list of default carry affordances.')
    PUT_IN_INVENTORY_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance used by carryable component to put objects in inventory.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    PUT_DOWN_HERE_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance used by carryable component to put down here via the\n        PutDownContinuationToken liability.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    PUT_DOWN_ANYWHERE_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance used by carryable component to put down objects anywhere\n        via the PutDownContinuationToken liability.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))

    def __init__(self,
                 owner,
                 put_down_tuning,
                 state_based_put_down_tuning,
                 carry_affordances,
                 provided_affordances,
                 allowed_hands,
                 holster_while_routing,
                 holster_compatibility,
                 unholster_on_long_route_only,
                 prefer_owning_sim_inventory_when_not_on_home_lot,
                 constraint_pick_up,
                 visibility_override=None,
                 display_name_override=None):
        super().__init__(owner)
        self.put_down_tuning = put_down_tuning
        self.state_based_put_down_tuning = state_based_put_down_tuning
        self.provided_affordances = provided_affordances
        self._attempted_putdown = False
        self._attempted_alternative_putdown = False
        self._carry_affordances = carry_affordances
        self.allowed_hands = allowed_hands
        self.holster_while_routing = holster_while_routing
        self.holster_compatibility = holster_compatibility
        self.unholster_on_long_route_only = unholster_on_long_route_only
        self.constraint_pick_up = constraint_pick_up
        self.prefer_owning_sim_inventory_when_not_on_home_lot = prefer_owning_sim_inventory_when_not_on_home_lot
        self._current_put_down_strategy = self.put_down_tuning

    @property
    def attempted_putdown(self):
        return self._attempted_putdown

    @property
    def attempted_alternative_putdown(self):
        return self._attempted_alternative_putdown

    @property
    def current_put_down_strategy(self):
        return self._current_put_down_strategy

    @property
    def ideal_slot_type_set(self):
        return self.current_put_down_strategy.ideal_slot_type_set

    @componentmethod
    def get_provided_affordances_gen(self):
        for affordance in self.provided_affordances:
            yield CarryTargetInteraction.generate(affordance, self.owner)

    def component_super_affordances_gen(self, **kwargs):
        if self._carry_affordances is None:
            affordances = self.DEFAULT_CARRY_AFFORDANCES
        else:
            affordances = self._carry_affordances
        for affordance in affordances:
            yield affordance

    def component_interactable_gen(self):
        yield self

    def on_state_changed(self, state, old_value, new_value):
        if new_value in self.state_based_put_down_tuning or old_value in self.state_based_put_down_tuning:
            self._generate_put_down_tuning()

    def _generate_put_down_tuning(self):
        for (state_value,
             put_down_strategy) in self.state_based_put_down_tuning.items():
            while self.owner.state_value_active(state_value):
                self._current_put_down_strategy = put_down_strategy
                break
        self._current_put_down_strategy = self.put_down_tuning

    @objects.components.componentmethod
    def get_put_down_aop(self,
                         interaction,
                         context,
                         alternative_multiplier=1,
                         own_inventory_multiplier=1,
                         object_inventory_multiplier=1,
                         in_slot_multiplier=1,
                         on_floor_multiplier=1,
                         visibility_override=None,
                         display_name_override=None,
                         additional_post_run_autonomy_commodities=None,
                         add_putdown_liability=False,
                         **kwargs):
        sim = interaction.sim
        owner = self.owner
        if not owner.transient:
            if not self.current_put_down_strategy.affordances:
                self._attempted_alternative_putdown = True
            if not self._attempted_alternative_putdown:
                self._attempted_alternative_putdown = True
                scored_aops = []
                for scored_aop in self._gen_affordance_score_and_aops(
                        interaction, multiplier=alternative_multiplier):
                    while scored_aop.aop.test(context):
                        scored_aops.append(scored_aop)
                if scored_aops:
                    scored_aops.sort(key=operator.itemgetter(0))
                    return scored_aops[-1].aop
            affordance = CarryableComponent.PUT_DOWN_ANYWHERE_AFFORDANCE
            slot_types_and_costs = self._get_slot_types_and_costs(
                multiplier=in_slot_multiplier)
            terrain_transform = self._get_terrain_transform(interaction)
            objects = self._get_objects_with_inventory(interaction)
            objects = [
                obj for obj in objects
                if obj.inventory_component.allow_putdown_in_inventory
            ]
            if self.current_put_down_strategy.floor_cost is not None and on_floor_multiplier is not None:
                world_cost = self.current_put_down_strategy.floor_cost * on_floor_multiplier
            else:
                world_cost = None
            if self.current_put_down_strategy.inventory_cost is not None and own_inventory_multiplier is not None:
                sim_inventory_cost = self.current_put_down_strategy.inventory_cost * own_inventory_multiplier
            else:
                sim_inventory_cost = None
            if self.current_put_down_strategy.object_inventory_cost is not None and object_inventory_multiplier is not None:
                object_inventory_cost = self.current_put_down_strategy.object_inventory_cost * object_inventory_multiplier
            else:
                object_inventory_cost = None
            aop = AffordanceObjectPair(
                affordance,
                self.owner,
                affordance,
                None,
                slot_types_and_costs=slot_types_and_costs,
                world_cost=world_cost,
                sim_inventory_cost=sim_inventory_cost,
                object_inventory_cost=object_inventory_cost,
                terrain_transform=terrain_transform,
                objects_with_inventory=objects,
                visibility_override=visibility_override,
                display_name_override=display_name_override,
                additional_post_run_autonomy_commodities=
                additional_post_run_autonomy_commodities,
                **kwargs)
            if add_putdown_liability:
                _add_putdown_liability_to_aop(aop, interaction)
            self._attempted_putdown = True
            return aop
        return self._get_destroy_aop(sim, **kwargs)

    def _gen_affordance_score_and_aops(self,
                                       interaction,
                                       multiplier=1,
                                       add_putdown_liability=False):
        for affordance in self.current_put_down_strategy.affordances:
            aop = AffordanceObjectPair(affordance, self.owner, affordance,
                                       None)
            if add_putdown_liability:
                _add_putdown_liability_to_aop(aop, interaction)
            yield ScoredAOP(multiplier, aop)

    def _get_cost_for_slot_type(self, slot_type):
        if slot_type in self.owner.ideal_slot_types:
            return self.current_put_down_strategy.preferred_slot_cost
        return self.current_put_down_strategy.normal_slot_cost

    def _get_slot_types_and_costs(self, multiplier=1):
        slot_types_and_costs = []
        for slot_type in self.owner.all_valid_slot_types:
            cost = self._get_cost_for_slot_type(slot_type)
            if cost is not None and multiplier is not None:
                cost *= multiplier
            else:
                cost = None
            slot_types_and_costs.append((slot_type, cost))
        return slot_types_and_costs

    def _get_terrain_transform(self, interaction):
        if self.owner.footprint_component is not None:
            sim = interaction.sim
            additional_put_down_distance = sim.posture_state.body.additional_put_down_distance
            starting_position = sim.position + sim.forward * (
                sim.object_radius + additional_put_down_distance)
            sim_los_constraint = sim.lineofsight_component.constraint
            if not sims4.geometry.test_point_in_compound_polygon(
                    starting_position, sim_los_constraint.geometry.polygon):
                starting_position = sim.position
            search_flags = FGLSearchFlag.STAY_IN_CURRENT_BLOCK | FGLSearchFlag.SHOULD_TEST_ROUTING | FGLSearchFlag.CALCULATE_RESULT_TERRAIN_HEIGHTS | FGLSearchFlag.DONE_ON_MAX_RESULTS | FGLSearchFlag.SHOULD_TEST_BUILDBUY
            MAX_PUTDOWN_STEPS = 8
            MAX_PUTDOWN_DISTANCE = 10
            (position, orientation) = placement.find_good_location(
                placement.FindGoodLocationContext(
                    starting_position=starting_position,
                    starting_orientation=sim.orientation,
                    starting_routing_surface=sim.routing_surface,
                    object_footprints=(self.owner.get_footprint(), ),
                    object_id=self.owner.id,
                    max_steps=MAX_PUTDOWN_STEPS,
                    max_distance=MAX_PUTDOWN_DISTANCE,
                    search_flags=search_flags))
            if position is not None:
                put_down_transform = sims4.math.Transform(
                    position, orientation)
                return put_down_transform

    def _get_objects_with_inventory(self, interaction):
        objects = []
        inventory_item = self.owner.inventoryitem_component
        if inventory_item is not None and CarryableComponent.PUT_IN_INVENTORY_AFFORDANCE is not None:
            while True:
                for obj in inventory_item.valid_object_inventory_gen():
                    objects.append(obj)
        return objects

    def _get_destroy_aop(self, sim, **kwargs):
        affordance = CarryableComponent.PUT_DOWN_HERE_AFFORDANCE
        return AffordanceObjectPair(affordance,
                                    self.owner,
                                    affordance,
                                    None,
                                    put_down_transform=None,
                                    **kwargs)

    def reset_put_down_count(self):
        self._attempted_alternative_putdown = False
        self._attempted_putdown = False
예제 #13
0
class AggregateMixerInteraction(MixerInteraction):
    INSTANCE_TUNABLES = {
        'aggregated_affordances':
        TunableList(
            description=
            '\n                A list of affordances composing this aggregate. A random one\n                will be chosen from sub-action weights if multiple interactions\n                pass at the same priority.\n                ',
            tunable=TunableTuple(
                description=
                '\n                    An affordance and priority entry.\n                    ',
                priority=Tunable(
                    description=
                    '\n                        The relative priority of this affordance compared to\n                        other affordances in this aggregate.\n                        ',
                    tunable_type=int,
                    default=0),
                affordance=MixerInteraction.TunableReference(
                    description=
                    '\n                        The aggregated affordance.\n                        ',
                    pack_safe=True)),
            tuning_group=GroupNames.GENERAL)
    }
    _allow_user_directed = True

    @classmethod
    def _aops_sorted_gen(cls,
                         target,
                         context,
                         super_interaction=DEFAULT,
                         **interaction_parameters):
        affordances = []
        source_interaction = context.sim.posture.source_interaction if super_interaction == DEFAULT else super_interaction
        for aggregated_affordance in cls.aggregated_affordances:
            aop = AffordanceObjectPair(aggregated_affordance.affordance,
                                       target, source_interaction.affordance,
                                       source_interaction,
                                       **interaction_parameters)
            affordances.append((aggregated_affordance.priority, aop))
        return sorted(affordances, key=operator.itemgetter(0), reverse=True)

    @classmethod
    def _test(cls, target, context, **interaction_parameters):
        result = super()._test(target, context, **interaction_parameters)
        if not result:
            return result
        cls._allow_user_directed = False
        context = context.clone_for_sim(sim=context.sim)
        for (_, aop) in cls._aops_sorted_gen(target, context,
                                             **interaction_parameters):
            result = aop.test(context)
            if result:
                if aop.affordance.allow_user_directed:
                    cls._allow_user_directed = True
                return result
        return TestResult(False, 'No sub-affordances passed their tests.')

    @classmethod
    def consumes_object(cls):
        for aggregated_affordance in cls.aggregated_affordances:
            if aggregated_affordance.affordance.consumes_object():
                return True
        return False

    @classproperty
    def allow_user_directed(cls):
        return cls._allow_user_directed

    def _do_perform_gen(self, timeline):
        context = self.context.clone_for_continuation(self)
        max_priority = None
        aops_valid = []
        invalid_aops_with_result = []
        for (priority, aop) in self._aops_sorted_gen(
                self.target,
                context,
                super_interaction=self.super_interaction,
                **self.interaction_parameters):
            if max_priority is not None:
                if priority < max_priority:
                    break
            test_result = aop.test(context)
            if test_result:
                aops_valid.append(aop)
                max_priority = priority
            else:
                invalid_aops_with_result.append((aop, test_result))
        if not aops_valid:
            logger.error(
                'Failed to find valid mixer affordance in AggregateMixerInteraction: {}, did we not run its test immediately before executing it?\n{}',
                self,
                invalid_aops_with_result,
                owner='rmccord')
            return ExecuteResult.NONE
            yield
        interactions_by_weight = []
        for aop in aops_valid:
            interaction_result = aop.interaction_factory(context)
            if not interaction_result:
                raise RuntimeError(
                    'Failed to generate interaction from aop {}. {} [rmccord]'.
                    format(aop, interaction_result))
            interaction = interaction_result.interaction
            if len(aops_valid) == 1:
                weight = 0
            else:
                weight = interaction.affordance.calculate_autonomy_weight(
                    context.sim)
            interactions_by_weight.append((weight, interaction))
        if not interactions_by_weight:
            return ExecuteResult.NONE
            yield
        (_, interaction) = max(interactions_by_weight,
                               key=operator.itemgetter(0))
        return AffordanceObjectPair.execute_interaction(interaction)
        yield
예제 #14
0
class AggregateSuperInteraction(SuperInteraction):
    INSTANCE_TUNABLES = {
        'aggregated_affordances':
        TunableList(
            description=
            '\n                A list of affordances composing this aggregate.  Distance\n                estimation will be used to break ties if there are multiple\n                valid interactions at the same priority level.\n                ',
            tunable=TunableTuple(
                description=
                '\n                    An affordance and priority entry.\n                    ',
                priority=Tunable(
                    description=
                    '\n                        The relative priority of this affordance compared to\n                        other affordances in this aggregate.\n                        ',
                    tunable_type=int,
                    default=0),
                affordance=SuperInteraction.TunableReference(
                    description=
                    '\n                        The aggregated affordance.\n                        ',
                    pack_safe=True)),
            tuning_group=GroupNames.GENERAL),
        'sim_to_push_affordance_on':
        TunableEnumEntry(
            description=
            '\n                The Sim to push the affordance on.  If this is Actor, the\n                affordance will be pushed as a continuation of this.\n                ',
            tunable_type=ParticipantType,
            default=ParticipantType.Actor,
            tuning_group=GroupNames.TRIGGERS),
        'use_aggregated_affordance_constraints':
        Tunable(
            description=
            "\n            If enabled, this interaction will pull it's constraints from the\n            interaction constraints of the aggregated affordances. The benefit\n            is that we are compatible with interactions we intend to run, even\n            if they have constraints different from one another. This prevents\n            us from having to add a bunch of tests to those affordances and a\n            generic constraint here.\n            ",
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.CONSTRAINTS)
    }
    _allow_user_directed = True

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._valid_aops = None

    @classproperty
    def affordances(cls):
        return (a.affordance.get_interaction_type()
                for a in cls.aggregated_affordances)

    @classmethod
    def _aops_sorted_gen(cls, target, **interaction_parameters):
        affordances = []
        for aggregated_affordance in cls.aggregated_affordances:
            aop = AffordanceObjectPair(aggregated_affordance.affordance,
                                       target,
                                       aggregated_affordance.affordance, None,
                                       **interaction_parameters)
            affordances.append((aggregated_affordance.priority, aop))
        return sorted(affordances, key=operator.itemgetter(0), reverse=True)

    @flexmethod
    def _get_tested_aops(cls, inst, target, context, **interaction_parameters):
        inst_or_cls = inst if inst is not None else cls
        if inst is not None and inst._valid_aops is not None:
            return inst._valid_aops
        aops_valid = []
        cls._allow_user_directed = False
        for (priority,
             aop) in inst_or_cls._aops_sorted_gen(target,
                                                  **interaction_parameters):
            test_result = aop.test(context)
            if test_result:
                if aop.affordance.allow_user_directed:
                    cls._allow_user_directed = True
                aops_valid.append((aop, priority))
        if inst is not None:
            inst._valid_aops = aops_valid
        return aops_valid

    @flexmethod
    def test(cls,
             inst,
             target=DEFAULT,
             context=DEFAULT,
             super_interaction=None,
             skip_safe_tests=False,
             **interaction_parameters):
        inst_or_cls = inst if inst is not None else cls
        result = super(__class__,
                       inst_or_cls).test(target=target,
                                         context=context,
                                         super_interaction=super_interaction,
                                         skip_safe_tests=skip_safe_tests,
                                         **interaction_parameters)
        if result:
            target = target if target is not DEFAULT else inst.target
            context = context if context is not DEFAULT else inst.context
            context = context.clone_for_sim(
                cls.get_participant(
                    participant_type=cls.sim_to_push_affordance_on,
                    sim=context.sim,
                    target=target))
            valid_aops = inst_or_cls._get_tested_aops(target, context,
                                                      **interaction_parameters)
            result = TestResult.TRUE if valid_aops else TestResult(
                False, 'No sub-affordances passed their tests.')
        return result

    @classmethod
    def consumes_object(cls):
        for affordance_tuple in cls.aggregated_affordances:
            if affordance_tuple.affordance.consumes_object():
                return True
        return False

    @classproperty
    def allow_user_directed(cls):
        return cls._allow_user_directed

    @flexmethod
    def _constraint_gen(cls,
                        inst,
                        sim,
                        target,
                        participant_type=ParticipantType.Actor,
                        **kwargs):
        inst_or_cls = cls if inst is None else inst
        yield from super(SuperInteraction, inst_or_cls)._constraint_gen(
            sim, target, participant_type=participant_type, **kwargs)
        if inst_or_cls.use_aggregated_affordance_constraints:
            aggregated_constraints = []
            affordances = []
            affordances = [
                aop.super_affordance for (aop, _) in inst._valid_aops
            ]
            affordances = affordances if not inst is not None or not inst._valid_aops is not None or affordances else [
                affordance_tuple.affordance
                for affordance_tuple in inst_or_cls.aggregated_affordances
            ]
            if not affordances:
                yield Nowhere
            for aggregated_affordance in affordances:
                intersection = ANYWHERE
                constraint_gen = aggregated_affordance.constraint_gen
                constraint_gen = super(SuperInteraction,
                                       aggregated_affordance)._constraint_gen
                for constraint in constraint_gen(
                        sim,
                        inst_or_cls.get_constraint_target(target),
                        participant_type=participant_type,
                        **kwargs):
                    intersection = constraint.intersect(intersection)
                    if not intersection.valid:
                        continue
                aggregated_constraints.append(intersection)
            if aggregated_constraints:
                yield create_constraint_set(
                    aggregated_constraints,
                    debug_name='AggregatedConstraintSet')

    def _do_perform_gen(self, timeline):
        sim = self.get_participant(self.sim_to_push_affordance_on)
        if sim == self.context.sim:
            context = self.context.clone_for_continuation(self)
        else:
            context = context.clone_for_sim(sim)
        max_priority = None
        aops_valid = []
        self._valid_aops = None
        valid_aops = self._get_tested_aops(self.target, context,
                                           **self.interaction_parameters)
        for (aop, priority) in valid_aops:
            if max_priority is not None:
                if priority < max_priority:
                    break
            aops_valid.append(aop)
            max_priority = priority
        if not aops_valid:
            logger.warn(
                'Failed to find valid super affordance in AggregateSuperInteraction: {}, did we not run its test immediately before executing it?',
                self)
            return ExecuteResult.NONE
            yield
        compatible_interactions = []
        for aop in aops_valid:
            interaction_result = aop.interaction_factory(context)
            if not interaction_result:
                raise RuntimeError(
                    'Failed to generate interaction from aop {}. {} [rmccord]'.
                    format(aop, interaction_result))
            interaction = interaction_result.interaction
            if self.use_aggregated_affordance_constraints:
                if interactions.si_state.SIState.test_compatibility(
                        interaction, force_concrete=True):
                    compatible_interactions.append(interaction)
            compatible_interactions.append(interaction)
        if not compatible_interactions:
            return ExecuteResult.NONE
            yield
        interactions_by_distance = []
        for interaction in compatible_interactions:
            if len(compatible_interactions) == 1:
                distance = 0
            else:
                (distance, _, _) = interaction.estimate_distance()
            if distance is not None:
                interactions_by_distance.append((distance, interaction))
            else:
                interactions_by_distance.append(
                    (sims4.math.MAX_INT32, interaction))
        (_, interaction) = min(interactions_by_distance,
                               key=operator.itemgetter(0))
        return AffordanceObjectPair.execute_interaction(interaction)
        yield
예제 #15
0
class ZoneModifier(HasTunableReference,
                   metaclass=HashedTunedInstanceMetaclass,
                   manager=services.get_instance_manager(
                       sims4.resources.Types.ZONE_MODIFIER)):
    INSTANCE_TUNABLES = {
        'zone_modifier_locked':
        Tunable(
            description=
            '\n            Whether this is a locked trait that cannot be assigned/removed\n            through build/buy.\n            ',
            tunable_type=bool,
            default=False,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI),
        'enter_lot_loot':
        TunableSet(
            description=
            '\n            Loot applied to Sims when they enter or spawn in on the lot while\n            this zone modifier is active.\n            \n            NOTE: The corresponding exit loot is not guaranteed to be given.\n            For example, if the Sim walks onto the lot, player switches to a\n            different zone, then summons that Sim, that Sim will bypass\n            getting the exit loot.\n            A common use case for exit lot loot is to remove buffs granted\n            by this zone_mod.  This case is already covered as buffs are \n            automatically removed if they are non-persistable (have no associated commodity)\n            ',
            tunable=LootActions.TunableReference(pack_safe=True),
            tuning_group=GroupNames.LOOT),
        'exit_lot_loot':
        TunableSet(
            description=
            '\n            Loot applied to Sims when they exit or spawn off of the lot while\n            this zone modifier is active.\n            \n            NOTE: This loot is not guaranteed to be given after the enter loot.\n            For example, if the Sim walks onto the lot, player switches to a\n            different zone, then summons that Sim, that Sim will bypass\n            getting the exit loot.\n            A common use case for exit lot loot is to remove buffs granted\n            by this zone_mod.  This case is already covered as buffs are \n            automatically removed if they are non-persistable (have no associated commodity)\n            ',
            tunable=LootActions.TunableReference(pack_safe=True),
            tuning_group=GroupNames.LOOT),
        'interaction_triggers':
        TunableList(
            description=
            '\n            A mapping of interactions to possible loots that can be applied\n            when an on-lot Sim executes them if this zone modifier is set.\n            ',
            tunable=ZoneInteractionTriggers.TunableFactory()),
        'schedule':
        ZoneModifierWeeklySchedule.TunableFactory(
            description=
            '\n            Schedule to be activated for this particular zone modifier.\n            '
        ),
        'household_actions':
        TunableList(
            description=
            '\n            Actions to apply to the household that owns this lot when this zone\n            modifier is set.\n            ',
            tunable=ZoneModifierHouseholdActionVariants(
                description=
                '\n                The action to apply to the household.\n                '
            )),
        'object_tag_to_actions':
        TunableMapping(
            description=
            '\n            Mapping of object tag to zone modifier from object actions. Objects \n            in this tuning can be buy objects, build objects (column, window, pool),\n            and materials (floor tiles, roof tiles, wallpaper).\n            \n            This is primarily intended for architectural elements such as wallpaper, \n            roof materials, windows will give effect to utilities and eco footprint.\n            \n            NOTE: The actions will only be applied if user enables the \n            "Architecture Affects Eco Living" option under Game Options.\n            ',
            key_type=TunableTag(
                description=
                '\n                The object tag that will be used to do actions.\n                '
            ),
            value_type=TunableList(
                description=
                '\n                The list of action to apply.\n                ',
                tunable=ZoneModifierFromObjectsActionVariants())),
        'prohibited_situations':
        OptionalTunable(
            description=
            '\n            Optionally define if this zone should prevent certain situations\n            from running or getting scheduled.\n            ',
            tunable=SituationIdentityTest.TunableFactory(
                description=
                '\n                Prevent a situation from running if it is one of the specified \n                situations or if it contains one of the specified tags.\n                '
            )),
        'venue_requirements':
        TunableVariant(
            description=
            '\n            Whether or not we use a blacklist or white list for the venue\n            requirements on this zone modifier.\n            ',
            allowed_venue_types=TunableSet(
                description=
                '\n                A list of venue types that this Zone Modifier can be placed on.\n                All other venue types are not allowed.\n                ',
                tunable=TunableReference(
                    description=
                    '\n                    A venue type that this Zone Modifier can be placed on.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.VENUE),
                    pack_safe=True)),
            prohibited_venue_types=TunableSet(
                description=
                '\n                A list of venue types that this Zone Modifier cannot be placed on.\n                ',
                tunable=TunableReference(
                    description=
                    '\n                    A venue type that this Zone Modifier cannot be placed on.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.VENUE),
                    pack_safe=True)),
            export_modes=ExportModes.All),
        'conflicting_zone_modifiers':
        TunableSet(
            description=
            '\n            Conflicting zone modifiers for this zone modifier. If the lot has any of the\n            specified zone modifiers, then it is not allowed to be equipped with this\n            one.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ZONE_MODIFIER),
                                     pack_safe=True),
            export_modes=ExportModes.All),
        'additional_situations':
        SituationCurve.TunableFactory(
            description=
            "\n            An additional schedule of situations that can be added in addition\n            a situation scheduler's source tuning.\n            ",
            get_create_params={'user_facing': False}),
        'zone_wide_loot':
        ZoneModifierUpdateAction.TunableFactory(
            description=
            '\n            Loots applied when spawning into a zone with \n            this zone modifier. This loot is also applied to all sims, \n            objects, etc. in the zone when this zone modifier is added to a lot.\n            ',
            tuning_group=GroupNames.LOOT),
        'cleanup_loot':
        ZoneModifierUpdateAction.TunableFactory(
            description=
            '\n            Loots applied when this zone modifier is removed.\n            ',
            tuning_group=GroupNames.LOOT),
        'on_add_loot':
        ZoneModifierUpdateAction.TunableFactory(
            description=
            '\n            Loots applied when this zone modifier is added.\n            ',
            tuning_group=GroupNames.LOOT),
        'spin_up_lot_loot':
        ZoneModifierUpdateAction.TunableFactory(
            description=
            '\n            Loots applied when the zone spins up.\n            ',
            tuning_group=GroupNames.LOOT),
        'utility_supply_surplus_loot':
        TunableMapping(
            description=
            '\n            Loots applied when utility supply statistic change\n            from deficit to surplus.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The utility that we want to listen for supply change.\n                ',
                tunable_type=Utilities,
                default=Utilities.POWER),
            value_type=ZoneModifierUpdateAction.TunableFactory(
                description=
                '\n                Loots to apply.\n                '),
            tuning_group=GroupNames.LOOT),
        'utility_supply_deficit_loot':
        TunableMapping(
            description=
            '\n            Loots applied when utility supply statistic change\n            from surplus to deficit.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The utility that we want to listen for supply change.\n                ',
                tunable_type=Utilities,
                default=Utilities.POWER),
            value_type=ZoneModifierUpdateAction.TunableFactory(
                description=
                '\n                Loots to apply.\n                '),
            tuning_group=GroupNames.LOOT),
        'ignore_route_events_during_zone_spin_up':
        Tunable(
            description=
            "\n            Don't handle sim route events during zone spin up.  Useful for preventing\n            unwanted loot from being applied when enter_lot_loot runs situation blacklist tests.\n            If we require sims to retrieve loot on zone spin up, we can tune spin_up_lot_loot. \n            ",
            tunable_type=bool,
            default=False),
        'hide_screen_slam':
        Tunable(
            description=
            '\n            If checked, this zone modifier will not show the usual screen slam\n            when first applied.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.UI)
    }
    _obj_tag_id_to_count = None

    @classproperty
    def obj_tag_id_to_count(cls):
        return cls._obj_tag_id_to_count

    @classmethod
    def on_start_actions(cls):
        cls.register_interaction_triggers()

    @classmethod
    def on_spin_up_actions(cls, is_build_eco_effects_enabled):
        sim_spawner_service = services.sim_spawner_service()
        if not sim_spawner_service.is_registered_sim_spawned_callback(
                cls.zone_wide_loot.apply_to_sim):
            sim_spawner_service.register_sim_spawned_callback(
                cls.zone_wide_loot.apply_to_sim)
        cls.spin_up_lot_loot.apply_all_actions()
        cls.zone_wide_loot.apply_all_actions()
        cls.apply_object_actions(is_build_eco_effects_enabled)

    @classmethod
    def on_add_actions(cls, is_build_eco_effects_enabled):
        sim_spawner_service = services.sim_spawner_service()
        if not sim_spawner_service.is_registered_sim_spawned_callback(
                cls.zone_wide_loot.apply_to_sim):
            sim_spawner_service.register_sim_spawned_callback(
                cls.zone_wide_loot.apply_to_sim)
        cls.register_interaction_triggers()
        cls.start_household_actions()
        cls.on_add_loot.apply_all_actions()
        cls.zone_wide_loot.apply_all_actions()
        cls.apply_object_actions(is_build_eco_effects_enabled)

    @classmethod
    def on_stop_actions(cls):
        services.sim_spawner_service().unregister_sim_spawned_callback(
            cls.zone_wide_loot.apply_to_sim)
        cls.unregister_interaction_triggers()
        cls.stop_household_actions()
        cls.revert_object_actions()

    @classmethod
    def on_remove_actions(cls):
        services.sim_spawner_service().unregister_sim_spawned_callback(
            cls.zone_wide_loot.apply_to_sim)
        cls.unregister_interaction_triggers()
        cls.stop_household_actions()
        cls.cleanup_loot.apply_all_actions()
        cls.revert_object_actions()

    @classmethod
    def on_utility_supply_surplus(cls, utility):
        if utility in cls.utility_supply_surplus_loot:
            cls.utility_supply_surplus_loot[utility].apply_all_actions()

    @classmethod
    def on_utility_supply_deficit(cls, utility):
        if utility in cls.utility_supply_deficit_loot:
            cls.utility_supply_deficit_loot[utility].apply_all_actions()

    @classmethod
    def handle_event(cls, sim_info, event, resolver):
        if event not in InteractionTestEvents:
            return
        sim = sim_info.get_sim_instance()
        if sim is None or not sim.is_on_active_lot():
            return
        for trigger in cls.interaction_triggers:
            trigger.handle_interaction_event(sim_info, event, resolver)

    @classmethod
    def start_household_actions(cls):
        if not cls.household_actions:
            return
        household_id = services.owning_household_id_of_active_lot()
        if household_id is not None:
            for household_action in cls.household_actions:
                household_action.start_action(household_id)

    @classmethod
    def stop_household_actions(cls):
        if not cls.household_actions:
            return
        household_id = services.owning_household_id_of_active_lot()
        if household_id is not None:
            for household_action in cls.household_actions:
                household_action.stop_action(household_id)

    @classmethod
    def _on_build_objects_environment_score_update(cls):
        household = services.active_household()
        if household is None:
            return
        for sim in household.instanced_sims_gen(
                allow_hidden_flags=ALL_HIDDEN_REASONS):
            sim.on_build_objects_environment_score_update()

    @classmethod
    def apply_object_actions(cls, is_build_eco_effects_enabled):
        if not is_build_eco_effects_enabled:
            return
        if not cls.object_tag_to_actions:
            return
        object_tags = list(cls.object_tag_to_actions.keys())
        curr_obj_tag_id_to_count = services.active_lot(
        ).get_object_count_by_tags(object_tags)
        if cls._obj_tag_id_to_count is None:
            delta_obj_tag_id_to_count = curr_obj_tag_id_to_count
        else:
            delta_obj_tag_id_to_count = {
                key:
                curr_obj_tag_id_to_count[key] - cls._obj_tag_id_to_count[key]
                for key in curr_obj_tag_id_to_count
            }
        zone = services.current_zone()
        for (obj_tag_id, obj_count) in delta_obj_tag_id_to_count.items():
            if obj_count != 0:
                for action in cls.object_tag_to_actions[Tag(obj_tag_id)]:
                    success = action.apply(obj_count)
                    if not success:
                        continue
                    if action.action_type == ZoneModifierFromObjectsActionType.STATISTIC_CHANGE:
                        zone.zone_architectural_stat_effects[
                            action.stat.guid64] += action.get_value(obj_count)
        cls._on_build_objects_environment_score_update()
        cls._obj_tag_id_to_count = curr_obj_tag_id_to_count

    @classmethod
    def revert_object_actions(cls):
        if not cls._obj_tag_id_to_count:
            return
        zone = services.current_zone()
        for (obj_tag_id, obj_count) in cls._obj_tag_id_to_count.items():
            if obj_count != 0:
                for action in cls.object_tag_to_actions[Tag(obj_tag_id)]:
                    success = action.revert(obj_count)
                    if not success:
                        continue
                    if action.action_type == ZoneModifierFromObjectsActionType.STATISTIC_CHANGE:
                        zone.zone_architectural_stat_effects[
                            action.stat.guid64] -= action.get_value(obj_count)
        cls._on_build_objects_environment_score_update()
        cls._obj_tag_id_to_count = None

    @classmethod
    def register_interaction_triggers(cls):
        services.get_event_manager().register_tests(cls,
                                                    cls._get_trigger_tests())

    @classmethod
    def unregister_interaction_triggers(cls):
        services.get_event_manager().unregister_tests(cls,
                                                      cls._get_trigger_tests())

    @classmethod
    def _get_trigger_tests(cls):
        tests = list()
        for trigger in cls.interaction_triggers:
            tests.extend(trigger.get_trigger_tests())
        return tests

    @classmethod
    def is_situation_prohibited(cls, situation_type):
        if cls.prohibited_situations is None:
            return False
        return cls.prohibited_situations(situation=situation_type)
예제 #16
0
class StreetBaseLootEffect(StreetEffect):
    INSTANCE_SUBCLASSES_ONLY = True
    INSTANCE_TUNABLES = {
        'enact_loot':
        TunableList(
            description=
            '\n            If enabled, Loot applied when the effect is enacted\n            ',
            tunable=LootActions.TunableReference(
                description=
                '\n                Loot applied when the effect is enacted.\n                ',
                pack_safe=True)),
        'repeal_loot':
        TunableList(
            description=
            '\n            If enabled, Loot applied when the effect is repealed\n            ',
            tunable=LootActions.TunableReference(
                description=
                '\n                Loot applied when the effect is repealed.\n                ',
                pack_safe=True)),
        'scheduled_loot':
        OptionalTunable(
            description=
            '\n            While enacted, loot to award on a schedule.\n            ',
            tunable=ScheduledLoot.TunableFactory())
    }

    @classmethod
    def _verify_tuning_callback(cls):
        pass

    @classmethod
    def _tuning_loaded_callback(cls):
        if cls.scheduled_loot is not None:
            cls.scheduled_loot = cls.scheduled_loot()

    def _collect_resolvers(self):
        raise NotImplementedError

    def _enact_for_resolver(self, resolver):
        for loot in self.enact_loot:
            loot.apply_to_resolver(resolver)

    def _repeal_for_resolver(self, resolver):
        for loot in self.repeal_loot:
            loot.apply_to_resolver(resolver)

    def _start_schedule(self):
        if self.scheduled_loot is not None:
            self.scheduled_loot.set_resolver_gen(self._collect_resolvers)
            self.scheduled_loot.start_loot_schedule()

    def finalize_startup(self, policy):
        super().finalize_startup(policy)
        if self._street is not None and self.policy.enacted:
            self._start_schedule()

    def enact(self):
        if self.enact_loot is None:
            return
        for resolver in self._collect_resolvers():
            self._enact_for_resolver(resolver)
        self._start_schedule()

    def repeal(self):
        if self.repeal_loot is None:
            return
        for resolver in self._collect_resolvers():
            self._repeal_for_resolver(resolver)
        if self.scheduled_loot is not None:
            self.scheduled_loot.set_resolver_gen(None)
            self.scheduled_loot.stop_loot_schedule()
예제 #17
0
class FestivalDramaNode(BaseDramaNode):
    GO_TO_FESTIVAL_INTERACTION = TunablePackSafeReference(description='\n        Reference to the interaction used to travel the Sims to the festival.\n        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))
    INSTANCE_TUNABLES = {'festival_open_street_director': TunableReference(description='\n            Reference to the open street director in question.\n            ', manager=services.get_instance_manager(sims4.resources.Types.OPEN_STREET_DIRECTOR)), 'street': TunableReference(description='\n            The street that this festival is allowed to run on.\n            ', manager=services.get_instance_manager(sims4.resources.Types.STREET)), 'scoring': OptionalTunable(description='\n            If enabled this DramaNode will be scored and chosen by the drama\n            service.\n            ', tunable=TunableTuple(description='\n                Data related to scoring this DramaNode.\n                ', base_score=TunableRange(description='\n                    The base score of this drama node.  This score will be\n                    multiplied by the score of the different filter results\n                    used to find the Sims for this DramaNode to find the final\n                    result.\n                    ', tunable_type=int, default=1, minimum=1), bucket=TunableEnumEntry(description="\n                    Which scoring bucket should these drama nodes be scored as\n                    part of.  Only Nodes in the same bucket are scored against\n                    each other.\n                    \n                    Change different bucket settings within the Drama Node's\n                    module tuning.\n                    ", tunable_type=DramaNodeScoringBucket, default=DramaNodeScoringBucket.DEFAULT), locked_args={'receiving_sim_scoring_filter': None})), 'pre_festival_duration': TunableSimMinute(description='\n            The amount of time in Sim minutes that this festival will be in a\n            pre-running state.  Testing against this Drama Node will consider\n            the node to be running, but the festival will not actually be.\n            ', default=120, minimum=1), 'fake_duration': TunableSimMinute(description="\n            The amount of time in Sim minutes that we will have this drama node\n            run when the festival isn't actually up and running.  When the\n            festival actually runs we will trust in the open street director to\n            tell us when we should actually end.\n            ", default=60, minimum=1), 'festival_dynamic_sign_info': OptionalTunable(description='\n            If enabled then this festival drama node can be used to populate\n            a dynamic sign.\n            ', tunable=TunableTuple(description='\n                Data for populating the dynamic sign view for the festival.\n                ', festival_name=TunableLocalizedString(description='\n                    The name of this festival.\n                    '), festival_time=TunableLocalizedString(description='\n                    The time that this festival should run.\n                    '), travel_to_festival_text=TunableLocalizedString(description='\n                    The text that will display to get you to travel to the festival.\n                    '), festival_not_started_tooltip=TunableLocalizedString(description='\n                    The tooltip that will display on the travel to festival\n                    button when the festival has not started.\n                    '), on_street_tooltip=TunableLocalizedString(description='\n                    The tooltip that will display on the travel to festival\n                    button when the player is already at the festival.\n                    '), on_vacation_tooltip=TunableLocalizedString(description='\n                    The tooltip that will display on the travel to festival\n                    button when the player is on vacation.\n                    '), display_image=TunableResourceKey(description='\n                     The image for this festival display.\n                     ', resource_types=sims4.resources.CompoundTypes.IMAGE), background_image=TunableResourceKey(description='\n                     The background image for this festival display.\n                     ', default=None, resource_types=sims4.resources.CompoundTypes.IMAGE), activity_info=TunableList(description='\n                    The different activities that are advertised to be running at this\n                    festival.\n                    ', tunable=TunableTuple(description='\n                        A single activity that will be taking place at this festival.\n                        ', activity_name=TunableLocalizedString(description='\n                            The name of this activity.\n                            '), activity_description=TunableLocalizedString(description='\n                            The description of this activity.\n                            '), icon=TunableIcon(description='\n                            The Icon that represents this festival activity.\n                            ')))), tuning_group=GroupNames.UI), 'starting_notification': OptionalTunable(description='\n            If enabled then when this festival runs we will surface a\n            notification to the players.\n            ', tunable=TunableTestedUiDialogNotificationSnippet(description='\n                The notification that will appear when this drama node runs.\n                '), tuning_group=GroupNames.UI), 'additional_drama_nodes': TunableList(description='\n            A list of additional drama nodes that we will score and schedule\n            when this drama node is run.  Only 1 drama node is run.\n            ', tunable=TunableReference(description='\n                A drama node that we will score and schedule when this drama\n                node is run.\n                ', manager=services.get_instance_manager(sims4.resources.Types.DRAMA_NODE))), 'delay_timeout': TunableSimMinute(description='\n            The amount of time in Sim minutes that the open street director has\n            been delayed that we will no longer start the festival.\n            ', default=120, minimum=0), 'travel_lot_override': OptionalTunable(description='\n            If enabled, sims will spawn at this lot instead of the Travel Lot \n            tuned on the street.\n            ', tunable=TunableLotDescription(description='\n                The specific lot that we will travel to when asked to travel to\n                this street.\n                ')), 'reject_same_street_travel': Tunable(description='\n            If True, we will disallow the drama node travel interaction to run\n            if the Sim is on the same street as the destination zone. If False,\n            same street travel will be allowed.\n            ', tunable_type=bool, default=True)}
    REMOVE_INSTANCE_TUNABLES = ('receiver_sim', 'sender_sim_info', 'picked_sim_info')

    @classproperty
    def drama_node_type(cls):
        return DramaNodeType.FESTIVAL

    @classproperty
    def persist_when_active(cls):
        return True

    @classproperty
    def simless(cls):
        return True

    @classmethod
    def get_travel_lot_id(cls, reject_same_street=False):
        if reject_same_street and cls.street is services.current_street():
            return
        if cls.travel_lot_override is not None:
            return get_lot_id_from_instance_id(cls.travel_lot_override)
        return get_lot_id_from_instance_id(cls.street.travel_lot)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._duration_alarm = None
        self._additional_nodes_processor = None

    def cleanup(self, from_service_stop=False):
        super().cleanup(from_service_stop=from_service_stop)
        if self._duration_alarm is not None:
            alarms.cancel_alarm(self._duration_alarm)
            self._duration_alarm = None
        if self._additional_nodes_processor is not None:
            self._additional_nodes_processor.trigger_hard_stop()
            self._additional_nodes_processor = None

    def _alarm_finished_callback(self, _):
        services.drama_scheduler_service().complete_node(self.uid)

    def _request_timed_out_callback(self):
        services.drama_scheduler_service().complete_node(self.uid)

    def _open_street_director_destroyed_early_callback(self):
        services.drama_scheduler_service().complete_node(self.uid)

    def _get_time_till_end(self):
        now = services.time_service().sim_now
        time_since_started = now - self._selected_time
        duration = create_time_span(minutes=self.fake_duration + self.pre_festival_duration)
        time_left_to_go = duration - time_since_started
        return time_left_to_go

    def _setup_end_alarm(self):
        time_left_to_go = self._get_time_till_end()
        self._duration_alarm = alarms.add_alarm(self, time_left_to_go, self._alarm_finished_callback)

    def _create_open_street_director_request(self):
        festival_open_street_director = self.festival_open_street_director(drama_node_uid=self._uid)
        preroll_time = self._selected_time + create_time_span(minutes=self.pre_festival_duration)
        request = OpenStreetDirectorRequest(festival_open_street_director, priority=festival_open_street_director.priority, preroll_start_time=preroll_time, timeout=create_time_span(minutes=self.delay_timeout), timeout_callback=self._request_timed_out_callback, premature_destruction_callback=self._open_street_director_destroyed_early_callback)
        services.venue_service().request_open_street_director(request)

    def _try_and_start_festival(self):
        street = services.current_street()
        if street is not self.street:
            self._setup_end_alarm()
            return
        self._create_open_street_director_request()

    def _process_scoring_gen(self, timeline):
        try:
            yield from services.drama_scheduler_service().score_and_schedule_nodes_gen(self.additional_drama_nodes, 1, street_override=self.street, timeline=timeline)
        except GeneratorExit:
            raise
        except Exception as exception:
            logger.exception('Exception while scoring DramaNodes: ', exc=exception, level=sims4.log.LEVEL_ERROR)
        finally:
            self._additional_nodes_processor = None

    def _pre_festival_alarm_callback(self, _):
        self._try_and_start_festival()
        services.get_event_manager().process_events_for_household(TestEvent.FestivalStarted, services.active_household())
        if self.starting_notification is not None:
            resolver = GlobalResolver()
            starting_notification = self.starting_notification(services.active_sim_info(), resolver=resolver)
            starting_notification.show_dialog(response_command_tuple=tuple([CommandArgType.ARG_TYPE_INT, self.guid64]))
        if self.additional_drama_nodes:
            sim_timeline = services.time_service().sim_timeline
            self._additional_nodes_processor = sim_timeline.schedule(elements.GeneratorElement(self._process_scoring_gen))

    def _setup_pre_festival_alarm(self):
        now = services.time_service().sim_now
        time_since_started = now - self._selected_time
        duration = create_time_span(minutes=self.pre_festival_duration)
        time_left_to_go = duration - time_since_started
        self._duration_alarm = alarms.add_alarm(self, time_left_to_go, self._pre_festival_alarm_callback)

    def _run(self):
        self._setup_pre_festival_alarm()
        services.get_event_manager().process_events_for_household(TestEvent.FestivalStarted, services.active_household())
        return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE

    def resume(self):
        now = services.time_service().sim_now
        time_since_started = now - self._selected_time
        if time_since_started < create_time_span(minutes=self.pre_festival_duration):
            self._setup_pre_festival_alarm()
        else:
            self._try_and_start_festival()

    def is_on_festival_street(self):
        street = services.current_street()
        return street is self.street

    def is_during_pre_festival(self):
        now = services.time_service().sim_now
        time_since_started = now - self._selected_time
        if time_since_started < create_time_span(minutes=self.pre_festival_duration):
            return True
        return False

    @classmethod
    def show_festival_info(cls):
        if cls.festival_dynamic_sign_info is None:
            return
        ui_info = cls.festival_dynamic_sign_info
        festival_info = UI_pb2.DynamicSignView()
        festival_info.drama_node_guid = cls.guid64
        festival_info.name = ui_info.festival_name
        lot_id = cls.get_travel_lot_id()
        persistence_service = services.get_persistence_service()
        zone_id = persistence_service.resolve_lot_id_into_zone_id(lot_id, ignore_neighborhood_id=True)
        zone_protobuff = persistence_service.get_zone_proto_buff(zone_id)
        if zone_protobuff is not None:
            festival_info.venue = LocalizationHelperTuning.get_raw_text(zone_protobuff.name)
        festival_info.time = ui_info.festival_time
        festival_info.image = sims4.resources.get_protobuff_for_key(ui_info.display_image)
        festival_info.background_image = sims4.resources.get_protobuff_for_key(ui_info.background_image)
        festival_info.action_label = ui_info.travel_to_festival_text
        running_nodes = services.drama_scheduler_service().get_running_nodes_by_class(cls)
        active_sim_info = services.active_sim_info()
        if all(active_node.is_during_pre_festival() for active_node in running_nodes):
            festival_info.disabled_tooltip = ui_info.festival_not_started_tooltip
        elif any(active_node.is_on_festival_street() for active_node in running_nodes):
            festival_info.disabled_tooltip = ui_info.on_street_tooltip
        elif active_sim_info.is_in_travel_group():
            festival_info.disabled_tooltip = ui_info.on_vacation_tooltip
        for activity in ui_info.activity_info:
            with ProtocolBufferRollback(festival_info.activities) as activity_msg:
                activity_msg.name = activity.activity_name
                activity_msg.description = activity.activity_description
                activity_msg.icon = create_icon_info_msg(IconInfoData(activity.icon))
        distributor = Distributor.instance()
        distributor.add_op(active_sim_info, GenericProtocolBufferOp(Operation.DYNAMIC_SIGN_VIEW, festival_info))

    @classmethod
    def travel_to_festival(cls):
        active_sim_info = services.active_sim_info()
        active_sim = active_sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS_EXCEPT_UNINITIALIZED)
        if active_sim is None:
            return
        lot_id = cls.get_travel_lot_id(reject_same_street=cls.reject_same_street_travel)
        if lot_id is None:
            return
        pick = PickInfo(pick_type=PickType.PICK_TERRAIN, lot_id=lot_id, ignore_neighborhood_id=True)
        context = interactions.context.InteractionContext(active_sim, interactions.context.InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT, interactions.priority.Priority.High, insert_strategy=interactions.context.QueueInsertStrategy.NEXT, pick=pick)
        active_sim.push_super_affordance(FestivalDramaNode.GO_TO_FESTIVAL_INTERACTION, None, context)
예제 #18
0
class StreetResidentSimLootEffect(StreetEffect):
    INSTANCE_TUNABLES = {
        'enact_loot':
        TunableList(
            description=
            "\n            If enabled, Loot applied on a Street's resident Sims when the effect is enacted\n            ",
            tunable=LootActions.TunableReference(
                description=
                "\n                Loot applied on a Street's resident Sims when the effect is enacted.\n                ",
                pack_safe=True)),
        'repeal_loot':
        TunableList(
            description=
            "\n            If enabled, Loot applied on a Street's resident Sims when the effect is repealed\n            ",
            tunable=LootActions.TunableReference(
                description=
                "\n                Loot applied on a Street's resident Sims when the effect is repealed.\n                ",
                pack_safe=True)),
        'scheduled_loot':
        OptionalTunable(
            description=
            '\n            While enacted, loot to award on a schedule.\n            ',
            tunable=ScheduledLoot.TunableFactory())
    }

    @classmethod
    def _verify_tuning_callback(cls):
        pass

    @classmethod
    def _tuning_loaded_callback(cls):
        if cls.scheduled_loot is not None:
            cls.scheduled_loot = cls.scheduled_loot()

    def _start_schedule(self):
        if self.scheduled_loot is not None:
            self.scheduled_loot.set_resolver_gen(lambda: [
                SingleSimResolver(sim_info)
                for sim_info in self.policy.provider.get_resident_sim_infos()
            ])
            self.scheduled_loot.start_loot_schedule()

    def _enact_for_sim_info(self, sim_info):
        resolver = SingleSimResolver(sim_info)
        for loot in self.enact_loot:
            loot.apply_to_resolver(resolver)

    def _repeal_for_sim_info(self, sim_info):
        resolver = SingleSimResolver(sim_info)
        for loot in self.repeal_loot:
            loot.apply_to_resolver(resolver)

    def finalize_startup(self, policy):
        super().finalize_startup(policy)
        if self._street is None:
            return

        def handle_moved_sim_info(sim_info, old_street, new_street):
            if not self.policy.enacted:
                return
            if old_street is self._street and self.repeal_loot is not None:
                self._repeal_for_sim_info(sim_info)
            if new_street is self._street and self.enact_loot is not None:
                self._enact_for_sim_info(sim_info)

        services.street_service(
        ).register_sim_info_home_street_change_callback(
            self._street, handle_moved_sim_info)
        if self.policy.enacted:
            self._start_schedule()

    def enact(self):
        if self.enact_loot is not None:
            for sim_info in self.policy.provider.get_resident_sim_infos():
                self._enact_for_sim_info(sim_info)
        self._start_schedule()

    def repeal(self):
        if self.repeal_loot is not None:
            for sim_info in self.policy.provider.get_resident_sim_infos():
                self._repeal_for_sim_info(sim_info)
        if self.scheduled_loot is not None:
            self.scheduled_loot.set_resolver_gen(None)
            self.scheduled_loot.stop_loot_schedule()
예제 #19
0
class Reward(HasTunableReference,
             metaclass=HashedTunedInstanceMetaclass,
             manager=services.get_instance_manager(
                 sims4.resources.Types.REWARD)):
    INSTANCE_SUBCLASSES_ONLY = True
    INSTANCE_TUNABLES = {
        'name':
        TunableLocalizedString(
            description=
            '\n            The display name for this reward.\n            ',
            allow_catalog_name=True,
            export_modes=ExportModes.All),
        'reward_description':
        OptionalTunable(
            description=
            '\n            If enabled, this text is used to describe this reward.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                Description for this reward.\n                ',
                export_modes=ExportModes.All),
            export_modes=ExportModes.All),
        'icon':
        TunableResourceKey(
            description=
            '\n            The icon image for this reward.\n            ',
            resource_types=sims4.resources.CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'tests':
        TunableTestSet(
            description=
            '\n            A series of tests that must pass in order for reward to be available.\n            '
        ),
        'rewards':
        TunableList(
            TunableVariant(
                description=
                '\n                The gifts that will be given for this reward. They can be either\n                a specific reward or a random reward, in the form of a list of\n                specific rewards.\n                ',
                specific_reward=TunableSpecificReward(),
                random_reward=TunableList(TunableRandomReward()))),
        'notification':
        OptionalTunable(
            description=
            '\n            If enabled, this notification will show when the sim/household receives this reward.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'reward_unavailable_tooltip':
        OptionalTunable(
            description=
            '\n            If enabled, this text will appear if a reward is unavailable. \n            Otherwise the default unavailable reward text is used.\n            ',
            tunable=TunableLocalizedStringFactory())
    }

    @classmethod
    def give_reward(cls, sim_info, disallowed_reward_types=()):
        raise NotImplementedError

    @classmethod
    def try_show_notification(cls, sim_info):
        if cls.notification is not None:
            dialog = cls.notification(sim_info, SingleSimResolver(sim_info))
            dialog.show_dialog()

    @classmethod
    def is_valid(cls, sim_info):
        if not cls.tests.run_tests(SingleSimResolver(sim_info)):
            return False
        for reward in cls.rewards:
            if not isinstance(reward, tuple):
                reward_instance = reward()
                return reward_instance.valid_reward(sim_info)
            for each_reward in reward:
                reward_instance = each_reward.reward()
                if not reward_instance.valid_reward(sim_info):
                    return False
            return True

    @classmethod
    def get_unavailable_tooltip(cls, sim_info):
        if cls.reward_unavailable_tooltip is not None:
            return cls.reward_unavailable_tooltip(sim_info)
예제 #20
0
class StreetInstancedSimLootEffect(StreetEffect):
    INSTANCE_TUNABLES = {
        'enact_loot':
        TunableList(
            description=
            '\n            Loots applied when a sim is instanced on a street where this effect\n            is enacted.\n            ',
            tunable=LootActions.TunableReference(pack_safe=True)),
        'repeal_loot':
        TunableList(
            description=
            '\n            Loots applied when a sim is de-instanced on a street where this\n            effect is enacted. \n            ',
            tunable=LootActions.TunableReference(pack_safe=True)),
        'scheduled_loot':
        OptionalTunable(
            description=
            '\n            While enacted, loot to award on a schedule.\n            ',
            tunable=ScheduledLoot.TunableFactory())
    }

    @classmethod
    def _verify_tuning_callback(cls):
        pass

    @classmethod
    def _tuning_loaded_callback(cls):
        if cls.scheduled_loot is not None:
            cls.scheduled_loot = cls.scheduled_loot()

    def _register_callbacks(self):
        street_service = services.street_service()
        street_service.register_sim_added_callback(self._street,
                                                   self._enact_for_sim_info)
        street_service.register_sim_removed_callback(self._street,
                                                     self._repeal_for_sim_info)

    def _unregister_callbacks(self):
        street_service = services.street_service()
        street_service.unregister_sim_added_callback(self._street,
                                                     self._enact_for_sim_info)
        street_service.unregister_sim_removed_callback(
            self._street, self._repeal_for_sim_info)

    def _enact_for_sim_info(self, sim_info):
        resolver = SingleSimResolver(sim_info)
        for loot in self.enact_loot:
            loot.apply_to_resolver(resolver)

    def _repeal_for_sim_info(self, sim_info):
        resolver = SingleSimResolver(sim_info)
        for loot in self.repeal_loot:
            loot.apply_to_resolver(resolver)

    def _start_schedule(self):
        if self.scheduled_loot is not None:
            self.scheduled_loot.set_resolver_gen(lambda: [
                SingleSimResolver(sim.sim_info)
                for sim in services.sim_info_manager().instanced_sims_gen()
            ])
            self.scheduled_loot.start_loot_schedule()

    def finalize_startup(self, policy):
        super().finalize_startup(policy)
        if self._street is None:
            return
        if self.policy.enacted and self._street is services.current_street():
            for sim in services.sim_info_manager().instanced_sims_gen():
                self._enact_for_sim_info(sim.sim_info)
            self._register_callbacks()
        if self.policy.enacted:
            self._start_schedule()

    def enact(self):
        if self.enact_loot is None:
            return
        for sim in services.sim_info_manager().instanced_sims_gen():
            self._enact_for_sim_info(sim.sim_info)
        self._register_callbacks()
        self._start_schedule()

    def repeal(self):
        if self.repeal_loot is None:
            return
        for sim in services.sim_info_manager().instanced_sims_gen():
            self._repeal_for_sim_info(sim.sim_info)
        self._unregister_callbacks()
        if self.scheduled_loot is not None:
            self.scheduled_loot.set_resolver_gen(None)
            self.scheduled_loot.stop_loot_schedule()
예제 #21
0
class ObjectCreationMixin:
    INVENTORY = 'inventory'
    CARRY = 'carry'
    INSTANCE_TUNABLES = FACTORY_TUNABLES = {
        'creation_data':
        TunableObjectCreationDataVariant(
            description=
            '\n            Define the object to create.\n            '),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the object as soon as it is created.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The state to apply and optional tests to decide if the state\n                should apply.\n                ',
                state=TunableStateValueReference(),
                tests=OptionalTunable(
                    description=
                    '\n                    If enabled, the state will only get set on the created\n                    object if the tests pass. Note: These tests can not be\n                    performed on the newly created object.\n                    ',
                    tunable=TunableTestSet()))),
        'destroy_on_placement_failure':
        Tunable(
            description=
            "\n            If checked, the created object will be destroyed on placement failure.\n            If unchecked, the created object will be placed into an appropriate\n            inventory on placement failure if possible.  If THAT fails, object\n            will be destroyed.\n            By default it goes into location target's inventory, you can use \n            fallback_location_target_override to make the created object go to\n            another participant's inventory.\n            ",
            tunable_type=bool,
            default=False),
        'owner_sim':
        TunableEnumEntry(
            description=
            '\n            The participant Sim whose household should own the object. Leave this\n            as Invalid to not assign ownership.\n            ',
            tunable_type=ParticipantTypeSingleSim,
            default=ParticipantType.Invalid),
        'location':
        TunableVariant(
            description=
            '\n            Where the object should be created.\n            ',
            default='position',
            position=_PlacementStrategyLocation.TunableFactory(),
            slot=_PlacementStrategySlot.TunableFactory(),
            inventory=TunableTuple(
                description=
                '\n                An inventory based off of the chosen Participant Type.\n                ',
                locked_args={'location': INVENTORY},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    "The owner of the inventory the object will be created in."\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor),
                mark_object_as_stolen_from_career=Tunable(
                    description=
                    '\n                    Marks the object as stolen from a career by the tuned location_target participant.\n                    This should only be checked if this basic extra is on a CareerSuperInteraction.\n                    ',
                    tunable_type=bool,
                    default=False),
                place_in_hidden_inventory=Tunable(
                    description=
                    '\n                    If True, the object is placed in the hidden inventory rather than the user-facing inventory.\n                    ',
                    tunable_type=bool,
                    default=False)),
            carry=TunableTuple(
                description=
                '\n                Carry the object. Note: This expects an animation in the\n                interaction to trigger the carry.\n                ',
                locked_args={'location': CARRY},
                carry_track_override=OptionalTunable(
                    description=
                    '\n                    If enabled, specify which carry track the Sim must use to carry the\n                    created object.\n                    ',
                    tunable=TunableEnumEntry(
                        description=
                        '\n                        Which hand to carry the object in.\n                        ',
                        tunable_type=PostureTrackGroup,
                        default=PostureTrack.RIGHT)))),
        'reserve_object':
        OptionalTunable(
            description=
            '\n            If this is enabled, the created object will be reserved for use by\n            the set Sim.\n            ',
            tunable=TunableEnumEntry(
                tunable_type=ParticipantTypeActorTargetSim,
                default=ParticipantTypeActorTargetSim.Actor)),
        'fallback_location_target_override':
        OptionalTunable(
            description=
            "\n            This will be ignored if destroy_on_placement_failure is checked. If this is enabled, we override fallback\n            location target.\n            Currently this is used when location target is different with the target whose inventory we want this\n            created object to go into. For example we want to create an object near another object but we want this\n            object to go to actor's inventory when placement fails.\n            ",
            tunable=TunableEnumEntry(tunable_type=ParticipantType,
                                     default=ParticipantType.Actor)),
        'notification_inventory':
        OptionalTunable(
            description=
            '\n            The notification to show when created object is placed in an inventory.\n            ',
            tunable=TunableTuple(
                participant_inventory=UiDialogNotification.TunableFactory(
                    description=
                    "\n                    The notification to show when created object is placed in a participant's (such as sim's) inventory.\n                    "
                ),
                household_inventory=UiDialogNotification.TunableFactory(
                    description=
                    '\n                    The notification to show when created object is placed in a household inventory.\n                    '
                ))),
        'temporary_tags':
        OptionalTunable(
            description=
            '\n            If enabled, these Tags are added to the created object and DO NOT\n            persist.\n            ',
            tunable=TunableSet(
                description=
                '\n                A set of temporary tags that are added to the created object.\n                These tags DO NOT persist.\n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    A tag that is added to the created object. This tag DOES\n                    NOT persist.\n                    ',
                    tunable_type=Tag,
                    default=Tag.INVALID),
                minlength=1)),
        'require_claim':
        Tunable(
            description=
            "\n            If checked, the created object will be claimed, and will need to\n            be reclaimed on load.  If it isn't reclaimed on load, the object\n            will be destroyed.\n            ",
            tunable_type=bool,
            default=False),
        'set_sim_as_owner':
        Tunable(
            description=
            '\n            If checked and owner_sim is set, the sim will also be set on the\n            object ownership component and not just the household.\n            ',
            tunable_type=bool,
            default=False),
        'set_value_to_crafted_tooltip':
        Tunable(
            description=
            '\n            If checked, the value will be set to the tooltip if this item has\n            a crafting component.\n            ',
            tunable_type=bool,
            default=True)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.resolver = None
        self._object_helper = None
        self._assigned_ownership = set()
        self._definition = None
        self._setup_params = None

    def initialize_helper(self, resolver, post_add=None):
        self._assigned_ownership.clear()
        self.resolver = resolver
        reserved_sim = None
        if self.reserve_object is not None:
            reserved_sim_info = self.resolver.get_participant(
                self.reserve_object)
            reserved_sim = reserved_sim_info.get_sim_instance()
        interaction = None
        if isinstance(self.resolver, InteractionResolver):
            interaction = self.resolver.interaction
        (self._definition,
         self._setup_params) = self.creation_data.get_creation_params(resolver)
        self._object_helper = CreateObjectHelper(
            reserved_sim,
            self._definition,
            interaction,
            object_to_clone=self.creation_data.get_source_object(
                self.resolver),
            init=self._setup_created_object,
            post_add=post_add)

    @property
    def definition(self):
        return self.creation_data.get_definition(self.resolver)

    def create_object(self, resolver):
        self.initialize_helper(resolver, post_add=self._place_object)
        created_object = self._object_helper.create_object()
        self._object_helper = None
        return created_object

    def _setup_created_object(self, created_object):
        self.creation_data.setup_created_object(self.resolver, created_object,
                                                **self._setup_params)
        if self.owner_sim != ParticipantType.Invalid:
            owner_sim = self.resolver.get_participant(self.owner_sim)
            if owner_sim is not None and owner_sim.is_sim:
                created_object.update_ownership(
                    owner_sim, make_sim_owner=self.set_sim_as_owner)
                self._assigned_ownership.add(created_object.id)
        for initial_state in self.initial_states:
            if created_object.state_component is None:
                created_object.add_component(StateComponent(created_object))
            if not initial_state.tests is None:
                if initial_state.tests.run_tests(self.resolver):
                    if created_object.has_state(initial_state.state.state):
                        created_object.set_state(initial_state.state.state,
                                                 initial_state.state,
                                                 from_creation=True)
            if created_object.has_state(initial_state.state.state):
                created_object.set_state(initial_state.state.state,
                                         initial_state.state,
                                         from_creation=True)
        if self.temporary_tags is not None:
            created_object.append_tags(self.temporary_tags)
        if created_object.has_component(
                objects.components.types.CRAFTING_COMPONENT):
            created_object.crafting_component.update_simoleon_tooltip()
            created_object.crafting_component.update_quality_tooltip()
            if self.set_value_to_crafted_tooltip:
                created_object.update_tooltip_field(
                    TooltipFieldsComplete.simoleon_value,
                    created_object.current_value)
        created_object.update_object_tooltip()
        if self.require_claim:
            created_object.claim()

    def _get_ignored_object_ids(self):
        pass

    def _place_object_no_fallback(self, created_object):
        if hasattr(self.location, 'try_place_object'):
            ignored_object_ids = self._get_ignored_object_ids()
            return self.location.try_place_object(
                created_object,
                self.resolver,
                ignored_object_ids=ignored_object_ids)
        elif self.location.location == self.CARRY:
            return True
        return False

    def _get_fallback_location_target(self, created_object):
        if self.fallback_location_target_override is not None:
            target_override = self.resolver.get_participant(
                self.fallback_location_target_override)
            if target_override is not None:
                return target_override
            logger.error(
                'Fallback location target override for participant {} and created object {} is none.\n                                Invalid participant?',
                self.fallback_location_target_override, created_object)
        if hasattr(self.location, '_get_reference_objects_gen'):
            for obj in self.location._get_reference_objects_gen(
                    created_object, self.resolver):
                return obj
        return self.resolver.get_participant(self.location.location_target)

    def _place_object(self, created_object):
        self._setup_created_object(created_object)
        if self._place_object_no_fallback(created_object):
            return True
        if not self.destroy_on_placement_failure:
            participant = self._get_fallback_location_target(created_object)
            if participant.is_sim:
                if isinstance(participant, sims.sim_info.SimInfo):
                    participant = participant.get_sim_instance(
                        allow_hidden_flags=ALL_HIDDEN_REASONS)
            location_type = getattr(self.location, 'location', None)
            if location_type == self.INVENTORY and self.location.mark_object_as_stolen_from_career:
                interaction = self.resolver.interaction
                if interaction is None:
                    logger.error(
                        'Mark Object As Stolen From Career is checked on CreateObject loot {}. \n                                    This should only be check on basic extra in a CareerSuperInteraction.',
                        self)
                    return False
                career_uid = interaction.interaction_parameters.get(
                    'career_uid')
                if career_uid is not None:
                    career = interaction.sim.career_tracker.get_career_by_uid(
                        career_uid)
                    if career is not None:
                        name_data = career.get_career_location(
                        ).get_persistable_company_name_data()
                        text = None
                        guid = None
                        if isinstance(name_data, str):
                            text = name_data
                        else:
                            guid = name_data
                        MarkObjectAsStolen.mark_object_as_stolen(
                            created_object,
                            stolen_from_text=text,
                            stolen_from_career_guid=guid)
                else:
                    logger.error(
                        'Interaction {} is tuned with a CreateObject basic extra that has mark_object_as_stolen_from_career as True,\n                                    but is not a CareerSuperInteraction. This is not supported.',
                        interaction)
            if created_object.inventoryitem_component is not None:
                if created_object.id not in self._assigned_ownership:
                    if participant.is_sim:
                        participant_household_id = participant.household.id
                    else:
                        participant_household_id = participant.get_household_owner_id(
                        )
                    created_object.set_household_owner_id(
                        participant_household_id)
                    self._assigned_ownership.add(created_object.id)
                if participant.inventory_component.player_try_add_object(
                        created_object,
                        hidden=location_type == self.INVENTORY
                        and self.location.place_in_hidden_inventory):
                    if self.notification_inventory:
                        notification = self.notification_inventory.participant_inventory(
                            participant, self.resolver)
                        notification.show_dialog()
                    return True
            sim = self.resolver.get_participant(ParticipantType.Actor)
            if not (participant is not None and participant.inventory_component
                    is not None and sim is None or not sim.is_sim):
                owning_household = services.owning_household_of_active_lot()
                if owning_household is not None:
                    for sim_info in owning_household.sim_info_gen():
                        if sim_info.is_instanced():
                            sim = sim_info.get_sim_instance()
                            break
            if sim is not None:
                if not sim.is_npc:
                    try:
                        created_object.set_household_owner_id(sim.household.id)
                        if build_buy.move_object_to_household_inventory(
                                created_object):
                            if self.notification_inventory:
                                notification = self.notification_inventory.household_inventory(
                                    sim, self.resolver)
                                notification.show_dialog()
                            return True
                        logger.error(
                            'Creation: Failed to place object {} in household inventory.',
                            created_object,
                            owner='rmccord')
                    except KeyError:
                        pass
        return False
예제 #22
0
class InventoryTypeTuning:
    INVENTORY_TYPE_DATA = TunableMapping(
        description=
        '\n        A mapping of Inventory Type to any static information required by the\n        client to display inventory data as well information about allowances\n        for each InventoryType.\n        ',
        key_type=InventoryType,
        value_type=TunableTuple(
            description=
            '\n            Any information required by the client to display inventory data.\n            ',
            skip_carry_pose_allowed=Tunable(
                description=
                '\n                If checked, an object tuned to be put away in this inventory\n                type will be allowed to skip the carry pose.  If unchecked, it\n                will not be allowed to skip the carry pose.\n                ',
                tunable_type=bool,
                default=False),
            put_away_allowed=Tunable(
                description=
                '\n                If checked, objects can be manually "put away" in this\n                inventory type. If unchecked, objects cannot be manually "put\n                away" in this inventory type.\n                ',
                tunable_type=bool,
                default=True),
            shared_between_objects=TunableEnumEntry(
                description=
                '\n                If shareable, this inventory will be shared between all objects\n                that have it. For example, if you put an item in one fridge,\n                you would be able to remove it from a different fridge on the\n                lot.',
                tunable_type=ObjectShareability,
                default=ObjectShareability.SHARED),
            max_inventory_size=OptionalTunable(tunable=TunableRange(
                description=
                '\n                    Max number of items inventory type can have\n                    ',
                tunable_type=int,
                default=sims4.math.MAX_INT32,
                minimum=1,
                maximum=sims4.math.MAX_INT32),
                                               disabled_name='unbounded',
                                               enabled_name='fixed_size')))
    GAMEPLAY_MODIFIERS = TunableMapping(
        description=
        "\n        A mapping of Inventory Type to the gameplay effects they provide. If an\n        inventory does not affect contained objects, it is fine to leave that\n        inventory's type out of this mapping.\n        ",
        key_type=InventoryType,
        value_type=TunableTuple(
            description='\n            Gameplay modifiers.\n            ',
            decay_modifiers=CommodityDecayModifierMapping(
                description=
                '\n                Multiply the decay rate of specific commodities by a tunable\n                integer in order to speed up or slow down decay while the\n                object is contained within this inventory. This modifier will\n                be multiplied with other modifiers on the object, if it has\n                any.\n                '
            ),
            decay_modifiers_tests=TunableTestSet(
                description=
                '\n                Set of tests that must be passed to apply decay modifiers.\n                '
            ),
            autonomy_modifiers=TunableList(
                description=
                '\n                Objects in the inventory of this object will have these\n                autonomy modifiers applied to them.\n                ',
                tunable=TunableAutonomyModifier(
                    description=
                    '\n                    Autonomy modifiers for objects that are placed in this\n                    inventory type.\n                    ',
                    locked_args={'relationship_multipliers': None}))))

    @classmethod
    def _verify_tuning_callback(cls):
        for inventory_type in set(InventoryType) - set(
                cls.INVENTORY_TYPE_DATA.keys()):
            logger.error(
                'Inventory type {} has no tuned inventory type data. This can be fixed in the tuning for objects.components.inventory_enum.tuning -> InventoryTypeTuning -> Inventory Type Data.',
                inventory_type.name,
                owner='bhill')

    @staticmethod
    def get_inventory_type_data_tuning(inventory_type):
        return InventoryTypeTuning.INVENTORY_TYPE_DATA.get(inventory_type)

    @staticmethod
    def get_gameplay_effects_tuning(inventory_type):
        return InventoryTypeTuning.GAMEPLAY_MODIFIERS.get(inventory_type)

    @staticmethod
    def is_shared_between_objects(inventory_type):
        tuning = InventoryTypeTuning.get_inventory_type_data_tuning(
            inventory_type)
        if tuning is None or tuning.shared_between_objects == ObjectShareability.SHARED:
            return True
        if tuning.shared_between_objects == ObjectShareability.NOT_SHARED:
            return False
        elif tuning.shared_between_objects == ObjectShareability.SHARED_IF_NOT_IN_APARTMENT:
            return not services.get_plex_service().is_zone_an_apartment(
                services.current_zone_id(),
                consider_penthouse_an_apartment=False)
        return True

    @staticmethod
    def is_put_away_allowed_on_inventory_type(inventory_type):
        tuning = InventoryTypeTuning.get_inventory_type_data_tuning(
            inventory_type)
        return tuning is None or tuning.put_away_allowed

    @staticmethod
    def get_max_inventory_size_for_inventory_type(inventory_type):
        tuning = InventoryTypeTuning.get_inventory_type_data_tuning(
            inventory_type)
        if tuning is None:
            return sims4.math.MAX_UINT32
        return tuning.max_inventory_size
예제 #23
0
class ObjectRouteFromTargetObject(_ObjectRoutingBehaviorBase):
    FACTORY_TUNABLES = {
        'radius':
        TunableDistanceSquared(
            description=
            '\n            Only objects within this distance are considered.\n            ',
            default=1),
        'target_type':
        TunableVariant(
            description=
            '\n            Type of target object to choose (object, sim).\n            ',
            object=_RouteTargetTypeObject.TunableFactory(),
            sim=_RouteTargetTypeSim.TunableFactory(),
            default='object'),
        'target_selection_test':
        TunableTestSet(
            description=
            '\n            A test used for selecting a target.\n            ',
            tuning_group=GroupNames.TESTS),
        'no_target_loot':
        TunableList(
            description=
            "\n            Loot to apply if no target is selected (eg, change state back to 'wander').\n            ",
            tunable=LootActions.TunableReference()),
        'constraints':
        TunableList(
            description=
            '\n            Constraints relative to the relative participant.\n            ',
            tunable=TunableGeometricConstraintVariant(
                description=
                '\n                Use the point on the found object defined by these geometric constraints.\n                ',
                disabled_constraints=('spawn_points',
                                      'spawn_points_with_backup'))),
        'target_action_rules':
        TunableList(
            description=
            '\n            A set of conditions and a list of one or more TargetObjectActions to run\n             on the target object after routing to it. These are applied in sequence.\n            ',
            tunable=_TargetActionRules.TunableFactory())
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if not cls.target_selection_test and not cls.tags:
            logger.error(
                'No selection test tuned for ObjectRouteFromTargetObject {}.',
                cls,
                owner='miking')

    def _find_target(self):
        all_objects = self.target_type.get_objects()
        objects = []
        for o in all_objects:
            dist_sq = (o.position - self._obj.position).magnitude_squared()
            if dist_sq > self.radius:
                continue
            if o == self:
                continue
            if not o.is_sim and not o.may_reserve(self._obj):
                continue
            if self.target_selection_test:
                resolver = DoubleObjectResolver(self._obj, o)
                if not self.target_selection_test.run_tests(resolver):
                    continue
            else:
                objects.append([o, dist_sq])
        if not objects:
            return
        source_handles = [
            routing.connectivity.Handle(self._obj.position,
                                        self._obj.routing_surface)
        ]
        dest_handles = []
        for o in objects:
            obj = o[0]
            parent = obj.parent
            route_to_obj = parent if parent is not None else obj
            constraint = Anywhere()
            for tuned_constraint in self.constraints:
                constraint = constraint.intersect(
                    tuned_constraint.create_constraint(self._obj,
                                                       route_to_obj))
            dests = constraint.get_connectivity_handles(self._obj, target=obj)
            if dests:
                dest_handles.extend(dests)
        if not dest_handles:
            return
        routing_context = self._obj.get_routing_context()
        connections = routing.estimate_path_batch(
            source_handles, dest_handles, routing_context=routing_context)
        if not connections:
            return
        connections.sort(key=lambda connection: connection[2])
        best_connection = connections[0]
        best_dest_handle = best_connection[1]
        best_obj = best_dest_handle.target
        return best_obj

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._target = self._find_target()

    def get_routes_gen(self):
        if self._target is None:
            self.on_no_target()
            return False
            yield
        routing_slot_constraint = Anywhere()
        for tuned_constraint in self.constraints:
            routing_slot_constraint = routing_slot_constraint.intersect(
                tuned_constraint.create_constraint(self._obj, self._target))
        goals = list(
            itertools.chain.from_iterable(
                h.get_goals()
                for h in routing_slot_constraint.get_connectivity_handles(
                    self._obj)))
        routing_context = self._obj.get_routing_context()
        route = routing.Route(self._obj.routing_location,
                              goals,
                              routing_context=routing_context)
        yield route

    def do_target_action_rules_gen(self, timeline):
        if not self.target_action_rules or self._target is None:
            return
        resolver = DoubleObjectResolver(self._obj, self._target)
        for target_action_rule in self.target_action_rules:
            if random.random.random() >= target_action_rule.chance:
                continue
            if not target_action_rule.test.run_tests(resolver):
                continue
            if target_action_rule.actions is not None:
                for action in target_action_rule.actions:
                    result = yield from action.run_action_gen(
                        timeline, self._obj, self._target)
                    if not result:
                        return
            if target_action_rule.abort_if_applied:
                return

    def on_no_target(self):
        resolver = SingleObjectResolver(self._obj)
        for loot_action in self.no_target_loot:
            loot_action.apply_to_resolver(resolver)
예제 #24
0
class DeathTracker(SimInfoTracker):
    DEATH_ZONE_ID = 0
    DEATH_TYPE_GHOST_TRAIT_MAP = TunableMapping(
        description=
        '\n        The ghost trait to be applied to a Sim when they die with a given death\n        type.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The death type to map to a ghost trait.\n            ',
            tunable_type=DeathType,
            default=DeathType.NONE),
        key_name='Death Type',
        value_type=TunableReference(
            description=
            '\n            The ghost trait to apply to a Sim when they die from the specified\n            death type.\n            ',
            manager=services.trait_manager()),
        value_name='Ghost Trait')
    DEATH_BUFFS = TunableList(
        description=
        '\n        A list of buffs to apply to Sims when another Sim dies. For example, use\n        this tuning to tune a "Death of a Good Friend" buff.\n        ',
        tunable=TunableTuple(
            test_set=TunableReference(
                description=
                "\n                The test that must pass between the dying Sim (TargetSim) and\n                the Sim we're considering (Actor). If this test passes, no\n                further test is executed.\n                ",
                manager=services.get_instance_manager(sims4.resources.Types.
                                                      SNIPPET),
                class_restrictions=('TestSetInstance', ),
                pack_safe=True),
            buff=TunableBuffReference(
                description=
                '\n                The buff to apply to the Sim.\n                ',
                pack_safe=True),
            notification=OptionalTunable(
                description=
                '\n                If enabled, an off-lot death generates a notification for the\n                target Sim. This is limited to one per death instance.\n                ',
                tunable=TunableUiDialogNotificationReference(
                    description=
                    '\n                    The notification to show.\n                    ',
                    pack_safe=True))))
    IS_DYING_BUFF = TunableReference(
        description=
        '\n        A reference to the buff a Sim is given when they are dying.\n        ',
        manager=services.buff_manager())
    DEATH_RELATIONSHIP_BIT_FIXUP_LOOT = TunableReference(
        description=
        '\n        A reference to the loot to apply to a Sim upon death.\n        \n        This is where the relationship bit fixup loots will be tuned. This\n        used to be on the interactions themselves but if the interaction was\n        reset then the bits would stay as they were. If we add more relationship\n        bits we want to clean up on death, the references Loot is the place to \n        do it.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.ACTION))

    def __init__(self, sim_info):
        self._sim_info = sim_info
        self._death_type = None
        self._death_time = None

    @property
    def death_type(self):
        return self._death_type

    @property
    def death_time(self):
        return self._death_time

    @property
    def is_ghost(self):
        return self._sim_info.trait_tracker.has_any_trait(
            self.DEATH_TYPE_GHOST_TRAIT_MAP.values())

    def get_ghost_trait(self):
        return self.DEATH_TYPE_GHOST_TRAIT_MAP.get(self._death_type)

    def set_death_type(self, death_type, is_off_lot_death=False):
        is_npc = self._sim_info.is_npc
        household = self._sim_info.household
        self._sim_info.inject_into_inactive_zone(self.DEATH_ZONE_ID,
                                                 start_away_actions=False,
                                                 skip_instanced_check=True,
                                                 skip_daycare=True)
        household.remove_sim_info(self._sim_info,
                                  destroy_if_empty_household=True)
        if is_off_lot_death:
            household.pending_urnstone_ids.append(self._sim_info.sim_id)
        self._sim_info.transfer_to_hidden_household()
        clubs.on_sim_killed_or_culled(self._sim_info)
        if death_type is None:
            return
        relationship_service = services.relationship_service()
        for target_sim_info in relationship_service.get_target_sim_infos(
                self._sim_info.sim_id):
            resolver = DoubleSimResolver(target_sim_info, self._sim_info)
            for death_data in self.DEATH_BUFFS:
                if not death_data.test_set(resolver):
                    continue
                target_sim_info.add_buff_from_op(
                    death_data.buff.buff_type,
                    buff_reason=death_data.buff.buff_reason)
                if is_npc and not target_sim_info.is_npc:
                    notification = death_data.notification(target_sim_info,
                                                           resolver=resolver)
                    notification.show_dialog()
                break
        ghost_trait = DeathTracker.DEATH_TYPE_GHOST_TRAIT_MAP.get(death_type)
        if ghost_trait is not None:
            self._sim_info.add_trait(ghost_trait)
        traits = list(self._sim_info.trait_tracker.equipped_traits)
        for trait in traits:
            if trait.remove_on_death:
                self._sim_info.remove_trait(trait)
        self._death_type = death_type
        self._death_time = services.time_service().sim_now.absolute_ticks()
        self._sim_info.reset_age_progress()
        self._sim_info.resend_death_type()
        self._handle_remove_rel_bits_on_death()
        services.get_event_manager().process_event(
            test_events.TestEvent.SimDeathTypeSet, sim_info=self._sim_info)

    def _handle_remove_rel_bits_on_death(self):
        resolver = SingleSimResolver(self._sim_info)
        if self.DEATH_RELATIONSHIP_BIT_FIXUP_LOOT is not None:
            for (loot,
                 _) in self.DEATH_RELATIONSHIP_BIT_FIXUP_LOOT.get_loot_ops_gen(
                 ):
                result = loot.test_resolver(resolver)
                if result:
                    loot.apply_to_resolver(resolver)

    def clear_death_type(self):
        self._death_type = None
        self._death_time = None
        self._sim_info.resend_death_type()

    def save(self):
        if self._death_type is not None:
            data = protocols.PersistableDeathTracker()
            data.death_type = self._death_type
            data.death_time = self._death_time
            return data

    def load(self, data):
        try:
            self._death_type = DeathType(data.death_type)
        except:
            self._death_type = DeathType.NONE
        self._death_time = data.death_time

    @classproperty
    def _tracker_lod_threshold(cls):
        return SimInfoLODLevel.MINIMUM
예제 #25
0
class GlobalLotTuningAndCleanup:
    __qualname__ = 'GlobalLotTuningAndCleanup'
    OBJECT_COUNT_TUNING = TunableMapping(
        description=
        '\n        Mapping between statistic and a set of tests that are run over the\n        objects on the lot on save.  The value of the statistic is set to the\n        number of objects that pass the tests.\n        ',
        key_type=TunableReference(
            description=
            '\n            The statistic on the lot that will be set the value of the number\n            of objects that pass the test set that it is mapped to.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC)),
        value_type=TunableTestSet(
            description=
            '\n            Test set that will be run on all objects on the lot to determine\n            what the value of the key statistic should be set to.\n            '
        ))
    SET_STATISTIC_TUNING = TunableList(
        description=
        '\n        A list of statistics and values that they will be set to on the lot\n        while saving it when the lot was running.\n        \n        These values are set before counting by tests on objects.\n        ',
        tunable=TunableTuple(
            statistic=TunableReference(
                description=
                '\n                The statistic that will have its value set.\n                ',
                manager=services.get_instance_manager(sims4.resources.Types.
                                                      STATISTIC)),
            amount=Tunable(
                description=
                '\n                The value that the statistic will be set to.\n                ',
                tunable_type=float,
                default=0.0)))
    OBJECT_CLEANUP_TUNING = TunableList(
        description=
        '\n        A list of actions to take when spinning up a zone in order to fix it\n        up based on statistic values that the lot has.\n        ',
        tunable=TunableTuple(
            count=TunableVariant(
                all_items=AllItems(),
                statistic_value=StatisticValue(),
                statistic_difference=StatisticDifference(),
                default='all_items',
                description=
                '\n                    The maximum number of items that will have the action run\n                    on them. \n                '
            ),
            possible_actions=
            TunableList(
                description=
                '\n                The different possible actions that can be taken on objects on\n                the lot if tests pass.\n                ',
                tunable=TunableTuple(actions=TunableList(
                    description=
                    '\n                        A group of actions to be taken on the object.\n                        ',
                    tunable=TunableVariant(
                        set_state=SetState(),
                        destroy_object=DestroyObject(),
                        statistic_change=StatisticChange(),
                        default='set_state',
                        description=
                        '\n                                The actual action that will be performed on the\n                                object if test passes.\n                            '
                    )),
                                     tests=TunableTestSet(
                                         description=
                                         '\n                        Tests that if they pass the object will be under\n                        consideration for this action being done on them.\n                        '
                                     )))))
    objects_to_destroy = None

    @classmethod
    def calculate_object_quantity_statistic_values(cls, lot):
        for set_statatistic in cls.SET_STATISTIC_TUNING:
            lot.set_stat_value(set_statatistic.statistic,
                               set_statatistic.amount)
        new_statistic_values = collections.defaultdict(int)
        for obj in services.object_manager().values():
            if obj.is_sim:
                pass
            if not obj.is_on_active_lot():
                pass
            resolver = SingleObjectResolver(obj)
            for (statistic, tests) in cls.OBJECT_COUNT_TUNING.items():
                while tests.run_tests(resolver):
                    new_statistic_values[statistic] += 1
        for (statistic, value) in new_statistic_values.items():
            lot.set_stat_value(statistic, value)

    @classmethod
    def cleanup_objects(cls, lot=None):
        if lot is None:
            logger.error('Lot is None when trying to run lot cleanup.',
                         owner='jjacobson')
            return
        cls.objects_to_destroy = set()
        for cleanup in GlobalLotTuningAndCleanup.OBJECT_CLEANUP_TUNING:
            items_to_cleanup = cleanup.count(lot)
            if items_to_cleanup == 0:
                pass
            items_cleaned_up = 0
            for obj in services.object_manager().values():
                if items_cleaned_up >= items_to_cleanup:
                    break
                if obj.is_sim:
                    pass
                resolver = SingleObjectResolver(obj)
                run_action = False
                for possible_action in cleanup.possible_actions:
                    while possible_action.tests.run_tests(resolver):
                        while True:
                            for action in possible_action.actions:
                                action(obj, lot)
                                run_action = True
                while run_action:
                    items_cleaned_up += 1
        for obj in cls.objects_to_destroy:
            obj.destroy(source=lot, cause='Cleaning up the lot')
        cls.objects_to_destroy = None
class TimedAspirationPickerInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'picker_dialog':
        UiItemPicker.TunableFactory(
            description=
            '\n            The timed aspiration picker dialog.\n            ',
            tuning_group=GroupNames.PICKERTUNING),
        'timed_aspirations':
        TunableList(
            description=
            '\n            The list of timed aspirations available to select.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ASPIRATION),
                                     class_restrictions='TimedAspiration',
                                     pack_safe=True),
            unique_entries=True,
            tuning_group=GroupNames.PICKERTUNING),
        'actor_continuation':
        TunableContinuation(
            description=
            '\n            If specified, a continuation to push on the actor when a picker \n            selection has been made.\n            ',
            locked_args={'actor': ParticipantType.Actor},
            tuning_group=GroupNames.PICKERTUNING),
        'loot_on_picker_selection':
        TunableList(
            description=
            "\n            Loot that will be applied to the Sim if an aspiration is selected.\n            It will not be applied if the user doesn't select an aspiration.\n            ",
            tunable=LootActions.TunableReference(),
            tuning_group=GroupNames.PICKERTUNING)
    }

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

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        resolver = SingleSimResolver(target.sim_info)
        for timed_aspiration in inst_or_cls.timed_aspirations:
            test_result = timed_aspiration.tests.run_tests(
                resolver, search_for_tooltip=True)
            is_enable = test_result.result
            if is_enable or test_result.tooltip is not None:
                if test_result.tooltip is not None:
                    row_tooltip = lambda *_, tooltip=test_result.tooltip, **__: inst_or_cls.create_localized_string(
                        tooltip)
                else:
                    row_tooltip = None
                row = BasePickerRow(
                    is_enable=is_enable,
                    name=inst_or_cls.create_localized_string(
                        timed_aspiration.display_name),
                    icon=timed_aspiration.display_icon,
                    row_description=inst_or_cls.create_localized_string(
                        timed_aspiration.display_description),
                    row_tooltip=row_tooltip,
                    tag=timed_aspiration)
                yield row

    def on_choice_selected(self, choice_tag, **kwargs):
        if choice_tag is None:
            return
        self.target.aspiration_tracker.activate_timed_aspiration(choice_tag)
        resolver = self.get_resolver()
        for loot_action in self.loot_on_picker_selection:
            loot_action.apply_to_resolver(resolver)
        if self.actor_continuation:
            self.push_tunable_continuation(self.actor_continuation)
예제 #27
0
 def __init__(self, **kwargs):
     super().__init__(
         object=TunableVenueObjectTags(
             description=
             "\n                Specify object tag(s) that must be on this venue. Allows you to\n                group objects, i.e. weight bench, treadmill, and basketball\n                goals are tagged as\n                'exercise objects.'\n                ",
             export_modes=ExportModes.All),
         object_parent_pair_tests=TunableList(
             description=
             "\n                Specify object tag(s) and/or parent attachment tags that\n                requires to be on this venue. Allows you to group objects, i.e.\n                weight bench, treadmill, and basketball goals are tagged as\n                'exercise objects.'\n                ",
             tunable=TunableTuple(
                 object_tags=TunableVenueObjectTags(
                     description=
                     '\n                        The objects (tag) that would count for the required items.\n                        ',
                     export_modes=ExportModes.All),
                 parent_tags=TunableVenueObjectTags(
                     description=
                     '\n                        If set, the object tuned in object_tags would required\n                        to be slotted to the parent object tuned in\n                        parent_tags. \n                        \n                        E.g. in restaurant, a chair (with restaurant_chair tag)\n                        would need to slot to a table (with\n                        restaurant_table_tag) to count as a dining slot. But\n                        since bar will not has the restaurant_table_tag, so a\n                        high chair that slots to the bar will not count as\n                        dining spot.\n                        ',
                     export_modes=ExportModes.All),
                 count=TunableRange(
                     description=
                     '\n                        How many required objects will be satisfied with this\n                        object(and/or with parent pair).\n                        \n                        E.g. a chair that slots to table will count as one\n                        dining spot, but booth slot to table will count as 2.\n                        ',
                     tunable_type=int,
                     default=1,
                     minimum=1),
                 required_object_test_tag=TunableEnumEntry(
                     tunable_type=VenueObjectTestTag,
                     default=VenueObjectTestTag.INVALID),
                 export_class_name='VenueObjectParentPairTuple',
                 export_modes=ExportModes.All)),
         min_number=TunableRange(
             description=
             '\n                The lower bound above which the number of objects of this type on\n                the lot must be.\n                ',
             tunable_type=int,
             default=0,
             minimum=0,
             export_modes=ExportModes.All),
         max_number=TunableRange(
             description=
             '\n                The upper bound below which the number of objects of this type on\n                the lot must be.\n                ',
             tunable_type=int,
             default=MAX_INT32,
             minimum=0,
             export_modes=ExportModes.All),
         object_display_name=TunableLocalizedString(
             description=
             '\n                Name that will be displayed for the object(s)\n                ',
             allow_catalog_name=True,
             export_modes=ExportModes.All),
         tooltip_override=TunableLocalizedString(
             description=
             '\n                If tuned, the tooltip that will be shown when this requirement\n                is moused over in the venue configuration requirements UI.\n                ',
             export_modes=ExportModes.All,
             allow_none=True),
         is_optional=Tunable(
             description=
             '\n                If True, this object requirement will be optional to this venue.\n                \n                E.g. Waiter station and host station for restaurant should set\n                this entry to True.\n                ',
             tunable_type=bool,
             default=False,
             export_modes=ExportModes.All),
         object_test_type=TunableEnumEntry(
             description=
             '\n                This option determines what test will be applied. To test the\n                number of objects of a certain type, select OBJECT. To test for\n                a pool, select pool. To test the number of tiles used by the\n                home, select tile (tiny home venues do this).\n                ',
             tunable_type=VenueObjectTestType,
             default=VenueObjectTestType.OBJECT),
         **kwargs)
예제 #28
0
class MotherPlantBattleSituation(SituationComplexCommon):
    MOTHER_PLANT_METER_ID = 1
    PLAYER_HEALTH_METER_ID = 2
    INSTANCE_TUNABLES = {
        'player_job':
        TunableReference(
            description=
            '\n            Job for the main player sim that fights the plant.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'player_sim_role_state':
        TunableReference(
            description=
            '\n            Role state for the main player sim Role.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'other_player_jobs':
        TunableReference(
            description=
            '\n            Job for the other player Sims that are not the main Sim and are not\n            participating as helpers.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'other_player_sims_role_state':
        TunableReference(
            description=
            '\n            Role state for the other player Sims.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'helper_1_job':
        TunableReference(
            description=
            '\n            Job for one of the helper Sims for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'helper_2_job':
        TunableReference(
            description=
            '\n            Job for one of the helper Sims for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'helper_3_job':
        TunableReference(
            description=
            '\n            Job for one of the helper Sims for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'helper_sim_prepare_role_state_1':
        TunableReference(
            description=
            '\n            Role state for helper Sim 1 when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'helper_sim_prepare_role_state_2':
        TunableReference(
            description=
            '\n            Role state for helper Sim 2 when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'helper_sim_prepare_role_state_3':
        TunableReference(
            description=
            '\n            Role state for helper Sim 3 when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'zombie_job':
        TunableReference(
            description=
            '\n            Job for the Zombies for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'zombie_prepare_role_state':
        TunableReference(
            description=
            '\n            Role state for the zombie Sims when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'zombie_fight_interaction':
        TunableReference(
            description=
            '\n            Interaction pushed on zombies to get them to fight a Sim.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'zombie_fight_interaction_timer':
        TunableSimMinute(
            description=
            '\n            Timer for the amount of time between zombie attacks.\n            ',
            minimum=1,
            default=30),
        'player_health_statistic':
        TunableReference(
            description=
            "\n            The statistic that we will use in order to determine the Sim's\n            health for the motherplant.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC)),
        'motherplant_health_statisic':
        TunableReference(
            description=
            "\n            The statistic that we will use in order to determine the Sim's\n            health for the motherplant.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC)),
        'victory_interaction_of_interest':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction of interest that we are looking for to determine\n            victory.\n            '
        ),
        'retreat_interaction_of_interest':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction of interest that we are looking for to determine\n            retreat.\n            '
        ),
        'loss_interaction_mixer':
        TunableReference(
            description=
            '\n            The affordance that will be pushed on the primary Sims if they\n            lose.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'fight_affordance':
        TunableReference(
            description=
            '\n            The primary fight interaction that we will use to run the defeat\n            mixer the player Sim.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'helper_victory_affordance':
        TunableReference(
            description=
            '\n            The affordance that will be pushed on the helper Sims if they\n            achieve victory.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'helper_lose_affordance':
        TunableReference(
            description=
            '\n            The affordance that will be pushed on the helper Sims if they\n            lose.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'mother_plant_definition':
        TunableReference(
            description=
            '\n            The actual mother plant itself.\n            ',
            manager=services.definition_manager()),
        'base_battle_situation_state':
        BattleThePlantSituationState.TunableFactory(
            locked_args={
                'allow_join_situation': True,
                'time_out': None
            },
            tuning_group=GroupNames.STATE),
        'attack_battle_situation_state':
        AttackBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'inspire_battle_situation_state':
        InspireBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'rally_battle_sitaution_state':
        RallyBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'warbling_warcry_battle_situation_state':
        WarblingWarcryBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'save_lock_tooltip':
        TunableLocalizedString(
            description=
            '\n            The tooltip/message to show when the player tries to save the game\n            while this situation is running. Save is locked when situation starts.\n            ',
            tuning_group=GroupNames.UI),
        'mother_plant_meter_settings':
        StatBasedSituationMeterData.TunableFactory(
            description=
            '\n            The meter used to track the health of the mother plant.\n            ',
            tuning_group=GroupNames.SITUATION,
            locked_args={'_meter_id': MOTHER_PLANT_METER_ID}),
        'player_health_meter_settings':
        StatBasedSituationMeterData.TunableFactory(
            description=
            '\n            The meter used to track the health of the player team.\n            ',
            tuning_group=GroupNames.SITUATION,
            locked_args={'_meter_id': PLAYER_HEALTH_METER_ID}),
        'mother_plant_icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed in the situation UI beside the mother plant\n            health bar.\n            ',
            resource_types=sims4.resources.CompoundTypes.IMAGE,
            default=None,
            allow_none=True,
            tuning_group=GroupNames.SITUATION),
        'states_to_set_on_start':
        TunableList(
            description=
            '\n            A list of states to set on the motherplant on start.\n            ',
            tunable=TunableStateValueReference(
                description=
                '\n                The state to set.\n                ')),
        'states_to_set_on_end':
        TunableList(
            description=
            '\n            A list of states to set on the motherplant on end.\n            ',
            tunable=TunableStateValueReference(
                description=
                '\n                The state to set.\n                ')),
        'victory_reward':
        TunableReference(
            description=
            '\n            The Reward received when the Sim wins the situation.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.REWARD)),
        'victory_audio_sting':
        TunableResourceKey(
            description=
            '\n            The sound to play when the Sim wins the battle.\n            ',
            resource_types=(sims4.resources.Types.PROPX, ),
            default=None,
            tuning_group=GroupNames.AUDIO),
        'defeat_audio_sting':
        TunableResourceKey(
            description=
            '\n            The sound to play when the Sim loses the battle.\n            ',
            resource_types=(sims4.resources.Types.PROPX, ),
            default=None,
            tuning_group=GroupNames.AUDIO),
        'possessed_buff':
        TunableBuffReference(
            description=
            '\n            Possessed Buff for zombie Sims. \n            ')
    }

    @property
    def user_facing_type(self):
        return SituationUserFacingType.MOTHER_PLANT_EVENT

    @property
    def situation_display_type(self):
        return SituationDisplayType.VET

    @property
    def situation_display_priority(self):
        return SituationDisplayPriority.VET

    @classmethod
    def _states(cls):
        return (SituationStateData(1, PrepareForBattleSituationState),
                SituationStateData.from_auto_factory(
                    2, cls.base_battle_situation_state),
                SituationStateData.from_auto_factory(
                    3, cls.attack_battle_situation_state),
                SituationStateData.from_auto_factory(
                    4, cls.inspire_battle_situation_state),
                SituationStateData.from_auto_factory(
                    5, cls.rally_battle_sitaution_state),
                SituationStateData.from_auto_factory(
                    6, cls.warbling_warcry_battle_situation_state))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return ((cls.player_job, cls.player_sim_role_state),
                (cls.other_player_jobs, cls.other_player_sims_role_state),
                (cls.helper_1_job, cls.helper_sim_prepare_role_state_1),
                (cls.helper_2_job, cls.helper_sim_prepare_role_state_2),
                (cls.helper_3_job, cls.helper_sim_prepare_role_state_3),
                (cls.zombie_job, cls.zombie_prepare_role_state))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._zombie_attack_alarm_handle = None
        self._registered_test_events = set()
        self._player_health_tracking_situation_goal = None
        self._statistic_watcher_handle = None
        self._victory = False

    @property
    def end_audio_sting(self):
        if self._victory:
            return self.victory_audio_sting
        return self.defeat_audio_sting

    def _get_reward(self):
        if self._victory:
            return self.victory_reward

    def _get_motherplant(self):
        return next(
            iter(services.object_manager().get_objects_of_type_gen(
                self.mother_plant_definition)))

    def _push_loss_on_player(self):
        motherplant = self._get_motherplant()
        for (sim, situation_sim) in self._situation_sims.items():
            if situation_sim.current_job_type is self.player_job:
                parent_si = sim.si_state.get_si_by_affordance(
                    self.fight_affordance)
                if parent_si is not None:
                    interaction_context = InteractionContext(
                        sim, InteractionSource.PIE_MENU, Priority.Critical)
                    aop = AffordanceObjectPair(self.loss_interaction_mixer,
                                               motherplant,
                                               self.fight_affordance,
                                               parent_si)
                    if not aop.test_and_execute(interaction_context):
                        logger.error(
                            'Attempting to push Motherplant Battle Ending Interaction, but failed.'
                        )
        self._push_interaction_on_all_helpers(self.helper_lose_affordance)

    def on_goal_completed(self, goal):
        super().on_goal_completed(goal)
        self._push_loss_on_player()
        self._self_destruct()

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        if job_type is self.zombie_job:
            sim.add_buff_from_op(self.possessed_buff.buff_type,
                                 buff_reason=self.possessed_buff.buff_reason)

    def _on_statistic_updated(self, stat_type, old_value, new_value):
        if stat_type is self.player_health_statistic:
            self._player_health_tracking_situation_goal.set_count(new_value)
            self._player_health_meter.send_update_if_dirty()
        elif stat_type is self.motherplant_health_statisic:
            self._mother_plant_meter.send_update_if_dirty()

    def _zombie_attack(self, _):
        if not self._cur_state.zombie_attack_valid:
            return
        zombies = []
        for (sim, situation_sim) in self._situation_sims.items():
            if situation_sim.current_job_type is self.zombie_job:
                zombies.append(sim)
        zombie_to_attack = random.choice(zombies)
        context = InteractionContext(
            sim,
            InteractionContext.SOURCE_SCRIPT,
            interactions.priority.Priority.High,
            insert_strategy=QueueInsertStrategy.NEXT,
            bucket=interactions.context.InteractionBucketType.DEFAULT)
        zombie_to_attack.push_super_affordance(self.zombie_fight_interaction,
                                               None, context)

    def _push_interaction_on_all_helpers(self, interaction_to_push):
        for (sim, situation_sim) in self._situation_sims.items():
            if not situation_sim.current_job_type is self.helper_1_job:
                if not situation_sim.current_job_type is self.helper_2_job:
                    if situation_sim.current_job_type is self.helper_3_job:
                        context = InteractionContext(
                            sim,
                            InteractionContext.SOURCE_SCRIPT,
                            interactions.priority.Priority.High,
                            insert_strategy=QueueInsertStrategy.NEXT,
                            bucket=interactions.context.InteractionBucketType.
                            DEFAULT)
                        sim.push_super_affordance(interaction_to_push, None,
                                                  context)
            context = InteractionContext(
                sim,
                InteractionContext.SOURCE_SCRIPT,
                interactions.priority.Priority.High,
                insert_strategy=QueueInsertStrategy.NEXT,
                bucket=interactions.context.InteractionBucketType.DEFAULT)
            sim.push_super_affordance(interaction_to_push, None, context)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event != TestEvent.InteractionComplete:
            return
        if resolver(self.victory_interaction_of_interest):
            self._push_interaction_on_all_helpers(
                self.helper_victory_affordance)
            self._victory = True
            self._self_destruct()
        elif resolver(self.retreat_interaction_of_interest):
            self._push_loss_on_player()
            self._self_destruct()

    def start_situation(self):
        services.get_persistence_service().lock_save(self)
        super().start_situation()
        self._change_state(PrepareForBattleSituationState())
        motherplant = self._get_motherplant()
        motherplant.set_stat_value(self.player_health_statistic, 0, add=True)
        motherplant.set_stat_value(self.motherplant_health_statisic,
                                   self.motherplant_health_statisic.max_value,
                                   add=True)
        for state_value in self.states_to_set_on_start:
            motherplant.set_state(state_value.state, state_value)
        statistic_tracker = motherplant.statistic_tracker
        self._statistic_watcher_handle = statistic_tracker.add_watcher(
            self._on_statistic_updated)
        self._setup_situation_meters()
        self._zombie_attack_alarm_handle = alarms.add_alarm(
            self,
            create_time_span(minutes=self.zombie_fight_interaction_timer),
            self._zombie_attack,
            repeating=True)
        for custom_key in itertools.chain(
                self.victory_interaction_of_interest.custom_keys_gen(),
                self.retreat_interaction_of_interest.custom_keys_gen()):
            custom_key_tuple = (TestEvent.InteractionComplete, custom_key)
            self._registered_test_events.add(custom_key_tuple)
            services.get_event_manager().register_with_custom_key(
                self, TestEvent.InteractionComplete, custom_key)

    def _setup_situation_meters(self):
        motherplant = self._get_motherplant()
        self._mother_plant_meter = self.mother_plant_meter_settings.create_meter_with_sim_info(
            self, motherplant)
        self._player_health_meter = self.player_health_meter_settings.create_meter_with_sim_info(
            self, motherplant)

    def build_situation_start_message(self):
        msg = super().build_situation_start_message()
        with ProtocolBufferRollback(msg.meter_data) as meter_data_msg:
            self.mother_plant_meter_settings.build_data_message(meter_data_msg)
        with ProtocolBufferRollback(msg.meter_data) as meter_data_msg:
            self.player_health_meter_settings.build_data_message(
                meter_data_msg)
        build_icon_info_msg(IconInfoData(icon_resource=self.mother_plant_icon),
                            None, msg.icon_info)
        return msg

    def _destroy(self):
        super()._destroy()
        services.get_persistence_service().unlock_save(self)
        for (event_type, custom_key) in self._registered_test_events:
            services.get_event_manager().unregister_with_custom_key(
                self, event_type, custom_key)
        motherplant = self._get_motherplant()
        statistic_tracker = motherplant.statistic_tracker
        statistic_tracker.remove_watcher(self._statistic_watcher_handle)
        for state_value in self.states_to_set_on_end:
            motherplant.set_state(state_value.state, state_value)
        self._registered_test_events.clear()
        if self._mother_plant_meter is not None:
            self._mother_plant_meter.destroy()
        if self._player_health_meter is not None:
            self._player_health_meter.destroy()

    def get_lock_save_reason(self):
        return self.save_lock_tooltip

    def set_motherplant_situation_state(self, motherplant_battle_state):
        if motherplant_battle_state == MotherplantBattleStates.ATTACK:
            self._change_state(self.attack_battle_situation_state())
        elif motherplant_battle_state == MotherplantBattleStates.INSPIRE:
            self._change_state(self.inspire_battle_situation_state())
        elif motherplant_battle_state == MotherplantBattleStates.RALLY:
            self._change_state(self.rally_battle_sitaution_state())
        elif motherplant_battle_state == MotherplantBattleStates.WARBLING_WARCRY:
            self._change_state(self.warbling_warcry_battle_situation_state())

    def _on_proxy_situation_goal_added(self, goal):
        self._player_health_tracking_situation_goal = goal

    def _issue_requests(self):
        super()._issue_requests()
        request = SelectableSimRequestFactory(
            self,
            _RequestUserData(),
            self.other_player_jobs,
            self.exclusivity,
            request_priority=BouncerRequestPriority.EVENT_DEFAULT_JOB)
        self.manager.bouncer.submit_request(request)
예제 #29
0
class GardeningTuning:
    INHERITED_STATE = ObjectState.TunableReference(
        description=
        '\n        Controls the state value that will be inherited by offspring.\n        '
    )
    SPONTANEOUS_GERMINATION_COMMODITY = Commodity.TunableReference()
    SPONTANEOUS_GERMINATION_COMMODITY_VARIANCE = TunableRange(
        description=
        '\n        Max variance to apply when the spawn commodity is reset.  This helps\n        plants all not to sprout from seeds at the same time.\n        ',
        tunable_type=int,
        default=10,
        minimum=0)
    SCALE_COMMODITY = Commodity.TunableReference()
    SCALE_VARIANCE = TunableInterval(
        description=
        "\n        Control how much the size of child fruit can vary from its father's\n        size.\n        ",
        tunable_type=float,
        default_lower=0.8,
        default_upper=1.2)
    EVOLUTION_STATE = ObjectState.TunableReference(
        description=
        '\n        Object state which will represent the icon behind the main icon of \n        the gardening tooltip.  This should be tied to the evolution state\n        of gardening objects.\n        '
    )
    SHOOT_DESCRIPTION_STRING = TunableLocalizedString(
        description=
        "\n        Text that will be given to a shoot description following ':' to its \n        fruit name.\n        e.g. 'Shoot taken from: Apple'\n        "
    )
    DISABLE_DETAILS_STATE_VALUES = TunableList(
        description=
        '\n            List of object state values where the gardening details should not \n            be shown.  This is for cases like Wild plants where we dont want\n            details that will not be used.\n            ',
        tunable=ObjectStateValue.TunableReference(
            description=
            '\n                The state that will disable the plant additional information.\n                '
        ))
    DISABLE_TOOLTIP_STATE_VALUES = TunableList(
        description=
        '\n            List of object state values where the gardening object will disable \n            its tooltip.\n            ',
        tunable=ObjectStateValue.TunableReference(
            description=
            '\n                The state that will disable the object tooltip.\n                '
        ))
    SPLICED_PLANT_NAME = TunableLocalizedStringFactory(
        description=
        '\n        Localized name to be set when a plant is spliced. \n        '
    )
    SPLICED_STATE_VALUE = ObjectStateValue.TunableReference(
        description=
        '\n        The state that will mean this plant has been already spliced.  \n        '
    )
    PICKUP_STATE_MAPPING = TunableMapping(
        description=
        '\n        Mapping that will set a state that should be set on the fruit when \n        its picked up, depending on a state fruit is currently in.\n        ',
        key_type=ObjectStateValue.TunableReference(),
        value_type=ObjectStateValue.TunableReference())
    GARDENING_SLOT = TunableReference(
        description=
        '\n        Slot type used by the gardening system to create its fruit.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SLOT_TYPE))
    GERMINATE_FAILURE_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        Notification that will tell the player that the plant has failed to\n        germinate.\n        '
    )
    UNIDENTIFIED_STATE_VALUE = ObjectStateValue.TunableReference(
        description=
        '\n        The state value all unidentified plants will have.  Remember to add this\n        as the default value for a state in the identifiable plants state\n        component tuning.\n        '
    )
    SEASONALITY_STATE = ObjectState.TunablePackSafeReference(
        description=
        "\n        A reference to the state that determines whether a plant is\n        Dormant/Indoors/In Season/Out of Season.\n        \n        The state value's display data is used in the UI tooltip for the plant.\n        "
    )
    SEASONALITY_IN_SEASON_STATE_VALUE = ObjectStateValue.TunablePackSafeReference(
        description=
        '\n        A reference to the state value that marks a plant as being In Season.\n        \n        This state value is determined to detect seasonality.\n        '
    )
    SEASONALITY_ALL_SEASONS_TEXT = TunableLocalizedString(
        description=
        '\n        The seasons text to display if the plant has no seasonality.\n        '
    )
    PLANT_SEASONALITY_TEXT = TunableLocalizedStringFactory(
        description=
        "\n        The text to display for the plant's seasonality.\n        e.g.:\n        Seasonality:\n{0.String}\n        "
    )
    FRUIT_STATES = TunableMapping(
        description=
        '\n        A mapping that defines which states on plants support fruits, and the\n        behavior when plants transition out of these states.\n        ',
        key_type=ObjectState.TunableReference(pack_safe=True),
        value_type=TunableTuple(
            states=TunableList(
                description=
                '\n                The list of states that supports fruit. If the object changes\n                state (for the specified state track) and the new value is not\n                in this list, the fruit is destroyed according to the specified\n                rule.\n                ',
                tunable=ObjectStateValue.TunableReference(pack_safe=True),
                unique_entries=True),
            behavior=
            TunableVariant(
                description=
                "\n                Define the fruit's behavior when plants exit a state that\n                supports fruit.\n                ",
                rot=TunablePercent(
                    description=
                    '\n                    Define the chance that the fruit falls and rots, as opposed\n                    to just being destroyed.\n                    ',
                    default=5),
                locked_args={'destroy': None},
                default='destroy')))
    FRUIT_DECAY_COMMODITY = TunableReference(
        description=
        '\n        The commodity that defines fruit decay (e.g. rotten/ripe).\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.STATISTIC))
    FRUIT_DECAY_COMMODITY_DROPPED_VALUE = Tunable(
        description=
        '\n        Value to set the Fruit Decay Commodity on a harvestable that has\n        been dropped from a plant during a seasonal transition.\n        ',
        tunable_type=int,
        default=10)
    SPAWN_WEIGHTS = TunableMapping(
        description=
        "\n        A fruit's chance to be spawned in a multi-fruit plant (e.g. via\n        splicing/grafting) is determined by its rarity.\n        \n        The weight is meant to curb the chance of spawning rarer fruits growing\n        on more common plants. It would never reduce the chance of the root\n        stock from spawning on its original plant.\n        \n        e.g.\n         A common Apple on a rare Pomegranate tree spawns at a 1:1 ratio.\n         A rare Pomegranate on a common Apple tree spawns at a 1:5 ratio.\n        ",
        key_type=TunableEnumEntry(tunable_type=ObjectCollectionRarity,
                                  default=ObjectCollectionRarity.COMMON),
        value_type=TunableRange(tunable_type=int, default=1, minimum=0))
    EXCLUSIVE_FRUITS = TunableSet(
        description=
        '\n        A set of fruits, which, when added onto a plant, can restrict\n        what other fruits the plant produces to this set of fruits. \n        This is done by adjusting spawn weight of non-exclusive fruits \n        on the plant to zero. \n        ',
        tunable=TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.OBJECT),
                                 pack_safe=True))
    VERTICAL_GARDEN_OBJECTS = TunableSet(
        description='\n        A set of Vertical garden objects.\n        ',
        tunable=TunableReference(manager=services.definition_manager(),
                                 pack_safe=True))

    @classmethod
    def is_spliced(cls, obj):
        if obj.has_state(cls.SPLICED_STATE_VALUE.state) and obj.get_state(
                cls.SPLICED_STATE_VALUE.state) == cls.SPLICED_STATE_VALUE:
            return True
        return False

    @classmethod
    def is_unidentified(cls, obj):
        if cls.UNIDENTIFIED_STATE_VALUE is not None and obj.has_state(
                cls.UNIDENTIFIED_STATE_VALUE.state) and obj.get_state(
                    cls.UNIDENTIFIED_STATE_VALUE.state
                ) == cls.UNIDENTIFIED_STATE_VALUE:
            return True
        return False

    @classmethod
    def get_seasonality_text_from_plant(cls, plant_definition):
        season_component = plant_definition.cls._components.season_aware_component
        if season_component is not None:
            seasons = []
            season_tuned_values = season_component._tuned_values
            for (season_type, season_states
                 ) in season_tuned_values.seasonal_state_mapping.items():
                if any(s is GardeningTuning.SEASONALITY_IN_SEASON_STATE_VALUE
                       for s in season_states):
                    season = SeasonsTuning.SEASON_TYPE_MAPPING[season_type]
                    seasons.append((season_type, season))
            if seasons:
                return GardeningTuning.PLANT_SEASONALITY_TEXT(
                    LocalizationHelperTuning.get_comma_separated_list(*tuple(
                        season.season_name
                        for (_, season) in sorted(seasons))))

    ALWAYS_GERMINATE_IF_NOT_SPAWNED_STATE = ObjectStateValue.TunableReference(
        description=
        '\n        If the specified state value is active on the gardening object, it will\n        have a 100% germination chance for when it is placed in the world in\n        any way other than through a spawner.\n        '
    )
    QUALITY_STATE_VALUE = ObjectState.TunableReference(
        description=
        '\n        The quality state all gardening plants will have.  \n        '
    )
예제 #30
0
class DisplayComponent(Component,
                       HasTunableFactory,
                       AutoFactoryInit,
                       component_name=types.DISPLAY_COMPONENT):
    DISPLAY_STATE = TunableStateValueReference(
        description=
        '\n        The state a display object will be set to when it is parented to a\n        Display Parent.\n        '
    )
    DEFAULT_STATE = TunableStateValueReference(
        description=
        '\n        The default state a display object will be set to when it is unparented\n        from a Display Parent.\n        '
    )
    FACTORY_TUNABLES = {
        'display_parent':
        CraftTaggedItemFactory(
            description=
            '\n            If an object matches the tag(s), it will be considered a Display\n            Parent for this display object. All display objects with a Display\n            Component MUST have a Display Parent tuned, otherwise there is no\n            need in the Display Component.\n            '
        ),
        'use_display_state':
        Tunable(
            description=
            "\n            If enabled, this object will change to the Display State when it is\n            parented to a Display Parent. The Display State is tuned in the\n            objects.components.display_component module tuning. NOTICE: If you\n            are only tuning this and not tuning any Inventory State Triggers,\n            it's recommended that you use the Slot Component in the Native\n            Components section of the parent object.\n            ",
            tunable_type=bool,
            default=True),
        'inventory_state_triggers':
        TunableList(
            description=
            '\n            Change states on the owning object based on tests applied to the\n            inventory of the Display Parent. Tests will be done in order and\n            will stop at the first success.\n            ',
            tunable=TunableTuple(inventory_test=InventoryTest.TunableFactory(),
                                 set_state=TunableStateValueReference()))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.inventory_state_triggers:
            services.get_event_manager().register_tests(
                self, (self.inventory_state_triggers[0].inventory_test, ))

    @property
    def _is_on_display_parent(self):
        parent = self.owner.parent
        if parent is None:
            return False
        return self.display_parent(crafted_object=parent,
                                   skill=None) is not None

    def handle_event(self, sim_info, event, resolver):
        if sim_info is not None:
            return
        if not self._is_on_display_parent:
            return
        self._handle_inventory_changed()

    def _handle_inventory_changed(self):
        obj_resolver = SingleObjectResolver(self.owner)
        for trigger in self.inventory_state_triggers:
            if obj_resolver(trigger.inventory_test):
                if self.owner.has_state(trigger.set_state.state):
                    self.owner.set_state(trigger.set_state.state,
                                         trigger.set_state)
                break

    def slotted_to_object(self, parent):
        if self._should_change_display_state(parent) and self.owner.has_state(
                self.DISPLAY_STATE.state):
            self.owner.set_state(self.DISPLAY_STATE.state, self.DISPLAY_STATE)
        self._handle_inventory_changed()

    def unslotted_from_object(self, parent):
        if self._should_change_display_state(parent) and self.owner.has_state(
                self.DEFAULT_STATE.state):
            self.owner.set_state(self.DEFAULT_STATE.state, self.DEFAULT_STATE)

    def _should_change_display_state(self, parent):
        if not self.use_display_state:
            return False
        return self.display_parent(crafted_object=parent, skill=None)