class LotDecorationPicker(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'by_location':
        TunableEnumSet(
            description=
            '\n            Filter for decorations available in any of these locations.\n            \n            If empty, this just returns all decorations.\n            ',
            enum_type=DecorationLocation),
        'picker_dialog':
        UiLotDecorationPicker.TunableFactory(
            description='\n            The item picker dialog.\n            ',
            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)
    }

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

    @classmethod
    def _items_gen(cls):
        if cls.by_location:
            for decoration in services.get_instance_manager(
                    Types.LOT_DECORATION).types.values():
                if decoration.available_locations & cls.by_location:
                    yield decoration
        else:
            yield from services.get_instance_manager(
                Types.LOT_DECORATION).types.values()

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        for item in cls._items_gen():
            display_name = LocalizationHelperTuning.get_raw_text(
                str(item.__name__)
            ) if item.display_name is None else item.display_name
            row_tooltip = None if item.display_tooltip is None else lambda *_, tooltip=item.display_tooltip: tooltip
            row = ObjectPickerRow(name=display_name,
                                  icon=item.display_icon,
                                  def_id=item.decoration_resource,
                                  row_tooltip=row_tooltip,
                                  row_description=item.display_description,
                                  tag_list=item.picker_categories,
                                  tag=item,
                                  use_catalog_product_thumbnails=False)
            yield row

    def on_choice_selected(self, choice, **kwargs):
        if choice is not None:
            self.push_tunable_continuation(self.actor_continuation,
                                           picked_item_ids=(choice.guid64, ),
                                           **kwargs)
class MultiPickerInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'picker_dialog':
        UiMultiPicker.TunableFactory(
            description=
            '\n           Tuning for the ui multi picker. \n           ',
            tuning_group=GroupNames.PICKERTUNING),
        'continuation':
        OptionalTunable(
            description=
            '\n            If enabled, you can tune a continuation to be pushed.\n            Do not use PickedObjects or PickedSims as we are not setting those\n            directly.\n            ',
            tunable=TunableContinuation(
                description=
                '\n                If specified, a continuation to push.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'success_notification':
        OptionalTunable(
            description=
            '\n            When enabled this dialog will be displayed when the multi picker\n            is accepted and has changes new information.\n            ',
            tunable=UiDialogNotification.TunableFactory(
                description=
                '\n                The notification that is displayed when a multi picker interaction\n                is accepted with new information.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING)
    }

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

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        return ()

    def on_choice_selected(self, choice_tag, **kwargs):
        if choice_tag:
            self._handle_successful_editing()

    def on_multi_choice_selected(self, choice_tags, **kwargs):
        if choice_tags:
            self._handle_successful_editing()

    def _push_continuation(self):
        if self.continuation is not None:
            self.push_tunable_continuation(self.continuation)

    def _handle_successful_editing(self):
        self._push_continuation()
        if self.success_notification is not None:
            resolver = self.get_resolver()
            dialog = self.success_notification(self.sim, resolver)
            dialog.show_dialog()
Пример #3
0
    class _ClubPickerActionPushInteraction(HasTunableSingletonFactory,
                                           AutoFactoryInit):
        FACTORY_TUNABLES = {
            'continuation':
            TunableContinuation(
                description=
                '\n                The interaction to push.\n                ',
                class_restrictions=(ClubSuperInteraction, ))
        }

        def on_choice_selected(self, interaction, picked_items, **kwargs):
            for club in picked_items:
                interaction.push_tunable_continuation(self.continuation,
                                                      associated_club=club)
Пример #4
0
class InteractionPickerItem(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'icon':
        OptionalTunable(
            description=
            '\n            If enabled, specify the icon to be displayed in UI.\n            ',
            tunable=TunableIconVariant()),
        'name':
        OptionalTunable(
            description=
            '\n            If enabled, display this name in the UI.\n            \n            Otherwise the display name of the first affordance\n            in the continuation will be used as the name.\n            ',
            tunable=TunableLocalizedStringFactory()),
        'item_description':
        OptionalTunable(
            description=
            '\n            When enabled, the tuned string will be shown as a description.\n            ',
            tunable=TunableLocalizedStringFactory()),
        'item_tooltip':
        OptionalTunable(
            description=
            '\n            When enabled, the tuned string will be shown as a tooltip.\n            ',
            tunable=TunableLocalizedStringFactory()),
        'disable_tooltip':
        OptionalTunable(
            description=
            '\n            When tuned, and the item is disabled, the tuned string \n            will be shown as a tooltip.\n            \n            Otherwise it will try to grab a tooltip off a failed test.\n            ',
            tunable=TunableLocalizedStringFactory()),
        'continuation':
        TunableContinuation(
            description=
            '\n            The continuation to push when this item is selected.\n            ',
            minlength=1),
        'enable_tests':
        OptionalTunable(
            description=
            '\n            Tests which would dictate if this option is enabled\n            in the pie menu.  ORs of ANDs.\n            \n            If disabled, it will default to the tests for the\n            first affordance in the continuation chain.\n            ',
            tunable=TunableTestSet()),
        'localization_tokens':
        OptionalTunable(
            description=
            "\n            Additional localization tokens for this item\n            to use in the name/description.\n            \n            This is in addition to the display name tokens\n            tuned in the continuation's first affordance.\n            ",
            tunable=LocalizationTokens.TunableFactory()),
        'visibility_tests':
        OptionalTunable(
            description=
            '\n            Tests which would dictate if this option is visible\n            in the pie menu.  ORs of ANDs.\n            \n            If disabled, this item will always be visible.\n            ',
            tunable=TunableTestSet())
    }
    class _OutfitPickerActionPushInteraction(HasTunableSingletonFactory,
                                             AutoFactoryInit):
        FACTORY_TUNABLES = {
            'continuation':
            TunableContinuation(
                description=
                '\n                The continuation to push. The selected outfits are the picked\n                item of the pushed interaction.\n                '
            )
        }

        def get_disabled_tooltip(self):
            pass

        def on_choice_selected(self, interaction, picked_items, **kwargs):
            interaction.push_tunable_continuation(
                self.continuation,
                insert_strategy=QueueInsertStrategy.LAST,
                picked_item_ids=picked_items)
class CreateCarriedObjectMixin:
    INTERACTION_PARAM_KEY = 'CreateCarriedObjectRuntimeObjectDefinition'
    INSTANCE_TUNABLES = {
        'definition':
        OptionalTunable(
            description=
            "\n            The object to create; this can be set at runtime.\n            \n            If 'runtime parameter' is chosen, it will look at the parameter \n            passed in at runtime to determine which object to create.\n            The primary use of the 'runtime parameter' option is if\n            the interaction is pushed from code so consult a GPE before using it.\n            ",
            tunable=TunableTestedVariant(
                description=
                '\n                The object to create.\n                ',
                tunable_type=ObjectDefinition(pack_safe=True)),
            tuning_group=GroupNames.CREATE_CARRYABLE,
            disabled_name='runtime_parameter',
            enabled_name='tuned_definition',
            enabled_by_default=True),
        'carry_track_override':
        OptionalTunable(
            description=
            '\n            If enabled, specify which carry track the Sim must use to carry the\n            created object.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableEnumEntry(
                description=
                '\n                Which hand to carry the object in.\n                ',
                tunable_type=PostureTrack,
                default=PostureTrack.RIGHT)),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the finished object as soon as it is\n            created.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableStateValueReference()),
        'continuation':
        SuperInteraction.TunableReference(
            description=
            '\n            An interaction to push as a continuation to the carry.\n            ',
            allow_none=True),
        'continuation_with_affordance_overrides':
        OptionalTunable(
            description=
            "\n            If enabled, allows you to specify a continuation to the\n            carry based on a participant's object definition.\n            This continuation will be pushed in addition to the tunable continuation,\n            although you will rarely need to tune both at the same time.\n            ",
            tunable=TunableTuple(
                continuation=TunableContinuation(
                    description=
                    '\n                    A tunable continuation to push based on the parameters provided.\n                    '
                ),
                participant=TunableEnumEntry(
                    description=
                    '\n                    When using the affordance_override mapping, this\n                    is the participant we will use to get the definition.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.PickedObject),
                affordance_override=TunableMapping(
                    description=
                    "\n                    Based on the participants's object definition, you can override\n                    the affordance on the tunable continuation.\n                    ",
                    key_type=TunableReference(
                        description=
                        '\n                        The object definition to look for.\n                        ',
                        manager=services.definition_manager()),
                    value_type=SuperInteraction.TunableReference())),
            tuning_group=GroupNames.CREATE_CARRYABLE)
    }

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

    @property
    def create_target(self):
        return self._get_chosen_definition()

    def _get_chosen_definition(self):
        if self._chosen_definition is None:
            if self.definition is None:
                self._chosen_definition = self.interaction_parameters.get(
                    self.INTERACTION_PARAM_KEY, None)
            else:
                self._chosen_definition = self.definition(
                    resolver=self.get_resolver())
        return self._chosen_definition

    def _get_create_continuation_affordance(self):
        def create_continuation_affordance():
            context = self.context.clone_for_continuation(self)
            aop = AffordanceObjectPair(self.continuation, self.created_target,
                                       self.continuation, None)
            return (aop, context)

        def create_continuation_affordance_with_overrides():
            obj = self.get_participant(
                self.continuation_with_affordance_overrides.participant)
            if obj is not None:
                affordance_override = self.continuation_with_affordance_overrides.affordance_override.get(
                    obj.definition)
            else:
                affordance_override = None
            interaction_parameters = {}
            if 'picked_item_ids' in self.interaction_parameters:
                interaction_parameters[
                    'picked_item_ids'] = self.interaction_parameters[
                        'picked_item_ids']
            for continuation in reversed(
                    self.continuation_with_affordance_overrides.continuation):
                local_actors = self.get_participants(continuation.actor)
                if self.sim in local_actors:
                    aops_and_context = self.get_continuation_aop_and_context(
                        continuation,
                        self.sim,
                        affordance_override=affordance_override,
                        **interaction_parameters)
                    if aops_and_context:
                        return aops_and_context
            return (None, None)

        if self.continuation_with_affordance_overrides is not None:
            return create_continuation_affordance_with_overrides
        elif self.continuation is not None:
            return create_continuation_affordance

    def build_basic_content(self, sequence, **kwargs):
        super_build_basic_content = super().build_basic_content

        def setup_object(obj):
            for initial_state in reversed(self.initial_states):
                obj.set_state(initial_state.state, initial_state)
            obj.set_household_owner_id(self.sim.household.id)

        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self._get_chosen_definition(),
            self,
            init=setup_object,
            tag='CreateCarriedObjectMixin')

        def claim_object(*_, **__):
            self._object_create_helper.claim()

        def set_carry_target(_):
            if self.carry_track_override:
                self.track = self.carry_track_override
            else:
                self.track = DEFAULT
            if self.track is None:
                return False
            self.map_create_target(self.created_target)

        def enter_carry(timeline):
            result = yield from element_utils.run_child(
                timeline,
                enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=claim_object,
                    create_si_fn=self._get_create_continuation_affordance(),
                    track=self.track,
                    sequence=build_critical_section(
                        super_build_basic_content(sequence, **kwargs),
                        flush_all_animations)))
            return result
            yield

        return (self._object_create_helper.create(set_carry_target,
                                                  enter_carry),
                lambda _: self._object_create_helper.claimed)
Пример #7
0
class AdventureMoment(HasTunableFactory, AutoFactoryInit):
    LOOT_NOTIFICATION_TEXT = TunableLocalizedStringFactory(description='\n        A string used to recursively build loot notification text. It will be\n        given two tokens: a loot display text string, if any, and the previously\n        built LOOT_NOTIFICATION_TEXT string.\n        ')
    NOTIFICATION_TEXT = TunableLocalizedStringFactory(description='\n        A string used to format notifications. It will be given two arguments:\n        the notification text and the built version of LOOT_NOTIFICATION_TEXT,\n        if not empty.\n        ')
    CHEAT_TEXT = TunableTuple(description='\n        Strings to be used for display text on cheat buttons to trigger all\n        adventure moments. \n        ', previous_display_text=TunableLocalizedStringFactory(description='\n            Text that will be displayed on previous cheat button.\n            '), next_display_text=TunableLocalizedStringFactory(description='\n            Text that will be displayed on next cheat button.\n            '), text_pattern=TunableLocalizedStringFactory(description='\n            Format for displaying next and previous buttons text including the\n            progress.\n            '), tooltip=TunableLocalizedStringFactory(description='\n            Tooltip to show when disabling previous or next button.\n            '))
    COST_TYPE_SIMOLEONS = 0
    COST_TYPE_ITEMS = 1
    CHEAT_PREVIOUS_INDEX = 1
    CHEAT_NEXT_INDEX = 2
    FACTORY_TUNABLES = {'description': '\n            A phase of an adventure. Adventure moments may present\n            some information in a dialog form and for a choice to be\n            made regarding how the overall adventure will branch.\n            ', '_visibility': OptionalTunable(description='\n            Control whether or not this moment provides visual feedback to\n            the player (i.e., a modal dialog).\n            ', tunable=UiDialog.TunableFactory(), disabled_name='not_visible', enabled_name='show_dialog'), '_finish_actions': TunableList(description='\n            A list of choices that can be made by the player to determine\n            branching for the adventure. They will be displayed as buttons\n            in the UI. If no dialog is displayed, then the first available\n            finish action will be selected. If this list is empty, the\n            adventure ends.\n            ', tunable=TunableTuple(availability_tests=TunableTestSet(description='\n                    A set of tests that must pass in order for this Finish\n                    Action to be available on the dialog. A Finish Action failing\n                    all tests is handled as if it were never tuned.\n                    '), display_text=TunableLocalizedStringFactoryVariant(description="\n                   This finish action's title. This will be the button text in\n                   the UI.\n                   ", allow_none=True), display_subtext=TunableLocalizedStringFactoryVariant(description='\n                    If tuned, this text will display below the button for this Finish Action.\n                    \n                    Span tags can be used to change the color of the text to green/positive and red/negative.\n                    <span class="positive">TEXT</span> will make the word TEXT green\n                    <span class="negative">TEXT</span> will make the word TEXT red\n                    ', allow_none=True), disabled_text=OptionalTunable(description="\n                    If enabled, this is the string that will be displayed if \n                    this finishing action is not available because the tests \n                    don't pass.\n                    ", tunable=TunableLocalizedStringFactory()), cost=TunableVariant(description='\n                    The cost associated with this finish action. Only one type\n                    of cost may be tuned. The player is informed of the cost\n                    before making the selection by modifying the display_text\n                    string to include this information.\n                    ', simoleon_cost=TunableTuple(description="The specified\n                        amount will be deducted from the Sim's funds.\n                        ", locked_args={'cost_type': COST_TYPE_SIMOLEONS}, amount=TunableRange(description='How many Simoleons to\n                            deduct.\n                            ', tunable_type=int, default=0, minimum=0)), item_cost=TunableTuple(description="The specified items will \n                        be removed from the Sim's inventory.\n                        ", locked_args={'cost_type': COST_TYPE_ITEMS}, item_cost=ItemCost.TunableFactory()), default=None), action_results=TunableList(description='\n                    A list of possible results that can occur if this finish\n                    action is selected. Action results can award loot, display\n                    notifications, and control the branching of the adventure by\n                    selecting the next adventure moment to run.\n                    ', tunable=TunableTuple(weight_modifiers=TunableList(description='\n                            A list of modifiers that affect the probability that\n                            this action result will be chosen. These are exposed\n                            in the form (test, multiplier). If the test passes,\n                            then the multiplier is applied to the running total.\n                            The default multiplier is 1. To increase the\n                            likelihood of this action result being chosen, tune\n                            multiplier greater than 1. To decrease the\n                            likelihood of this action result being chose, tune\n                            multipliers lower than 1. If you want to exclude\n                            this action result from consideration, tune a\n                            multiplier of 0.\n                            ', tunable=TunableTuple(description='\n                                A pair of test and weight multiplier. If the\n                                test passes, the associated weight multiplier is\n                                applied. If no test is specified, the multiplier\n                                is always applied.\n                                ', test=TunableTestVariant(description='\n                                    The test that has to pass for this weight\n                                    multiplier to be applied. The information\n                                    available to this test is the same\n                                    information available to the interaction\n                                    owning this adventure.\n                                    ', test_locked_args={'tooltip': None}), weight_multiplier=Tunable(description='\n                                    The weight multiplier to apply if the\n                                    associated test passes.\n                                    ', tunable_type=float, default=1))), notification=OptionalTunable(description='\n                            If set, this notification will be displayed.\n                            ', tunable=TunableUiDialogNotificationSnippet()), next_moments=TunableList(description='\n                            A list of adventure moment keys. One of these keys will\n                            be selected to determine which adventure moment is\n                            selected next. If the list is empty, the adventure ends\n                            here. Any of the keys tuned here will have to be tuned\n                            in the _adventure_moments tunable for the owning adventure.\n                            ', tunable=AdventureMomentKey), loot_actions=TunableList(description='\n                            List of Loot actions that are awarded if this action result is selected.\n                            ', tunable=LootActions.TunableReference()), continuation=TunableContinuation(description='\n                            A continuation to push when running finish actions.\n                            '), results_dialog=OptionalTunable(description='\n                            A results dialog to show. This dialog allows a list\n                            of icons with labels.\n                            ', tunable=UiDialogLabeledIcons.TunableFactory()), events_to_send=TunableList(description='\n                            A list of events to send.\n                            ', tunable=TunableEnumEntry(description='\n                                events types to send\n                                ', tunable_type=TestEvent, default=TestEvent.Invalid))))))}

    def __init__(self, parent_adventure, **kwargs):
        super().__init__(**kwargs)
        self._parent_adventure = parent_adventure
        self.resolver = self._interaction.get_resolver()

    @property
    def _interaction(self):
        return self._parent_adventure.interaction

    @property
    def _sim(self):
        return self._interaction.sim

    def run_adventure(self):
        if self._visibility is None:
            if self._finish_actions:
                self._run_first_valid_finish_action()
        else:
            dialog = self._get_dialog()
            if dialog is not None:
                self._parent_adventure.force_action_result = True
                dialog.show_dialog(auto_response=0)

    def _run_first_valid_finish_action(self):
        resolver = self.resolver
        for (action_id, finish_action) in enumerate(self._finish_actions):
            if finish_action.availability_tests.run_tests(resolver):
                return self._run_action_from_index(action_id)

    def _is_action_result_available(self, action_result):
        if not action_result.next_moments:
            return True
        for moment_key in action_result.next_moments:
            if self._parent_adventure.is_adventure_moment_available(moment_key):
                return True
        return False

    def _run_action_from_cheat(self, action_index):
        cheat_index = action_index - len(self._finish_actions) + 1
        if cheat_index == self.CHEAT_PREVIOUS_INDEX:
            self._parent_adventure.run_cheat_previous_moment()
        elif cheat_index == self.CHEAT_NEXT_INDEX:
            self._parent_adventure.run_cheat_next_moment()

    def _get_action_result_weight(self, action_result):
        interaction_resolver = self.resolver
        weight = 1
        for modifier in action_result.weight_modifiers:
            if not modifier.test is None:
                if interaction_resolver(modifier.test):
                    weight *= modifier.weight_multiplier
            weight *= modifier.weight_multiplier
        return weight

    def _apply_action_cost(self, action):
        if action.cost.cost_type == self.COST_TYPE_SIMOLEONS:
            if not self._sim.family_funds.try_remove(action.cost.amount, Consts_pb2.TELEMETRY_INTERACTION_COST, sim=self._sim):
                return False
        elif action.cost.cost_type == self.COST_TYPE_ITEMS:
            item_cost = action.cost.item_cost
            return item_cost.consume_interaction_cost(self._interaction)()
        return True

    def _run_action_from_index(self, action_index):
        try:
            finish_action = self._finish_actions[action_index]
        except IndexError as err:
            logger.exception('Exception {} while attempting to get finish action.\nFinishActions length: {}, ActionIndex: {},\nCurrent Moment: {},\nResolver: {}.\n', err, len(self._finish_actions), action_index, self._parent_adventure._current_moment_key, self.resolver)
            return
        forced_action_result = False
        weight_pairs = [(self._get_action_result_weight(action_result), action_result) for action_result in finish_action.action_results if self._is_action_result_available(action_result)]
        if not weight_pairs:
            if self._parent_adventure.force_action_result:
                forced_action_result = True
                weight_pairs = [(self._get_action_result_weight(action_result), action_result) for action_result in finish_action.action_results]
        action_result = weighted_random_item(weight_pairs)
        if not (action_result is not None or not finish_action.action_results) and not self._apply_action_cost(finish_action):
            return
        if action_result is not None:
            loot_display_text = None
            resolver = self.resolver
            for actions in action_result.loot_actions:
                for (loot_op, test_ran) in actions.get_loot_ops_gen(resolver):
                    (success, _) = loot_op.apply_to_resolver(resolver, skip_test=test_ran)
                    if success and action_result.notification is not None:
                        current_loot_display_text = loot_op.get_display_text()
                        if current_loot_display_text is not None:
                            if loot_display_text is None:
                                loot_display_text = current_loot_display_text
                            else:
                                loot_display_text = self.LOOT_NOTIFICATION_TEXT(loot_display_text, current_loot_display_text)
            if action_result.notification is not None:
                if loot_display_text is not None:
                    notification_text = lambda *tokens: self.NOTIFICATION_TEXT(action_result.notification.text(*tokens), loot_display_text)
                else:
                    notification_text = action_result.notification.text
                dialog = action_result.notification(self._sim, self.resolver)
                dialog.text = notification_text
                dialog.show_dialog()
            if action_result.next_moments:
                if forced_action_result:
                    next_moment_key = random.choice(action_result.next_moments)
                else:
                    next_moment_key = random.choice(tuple(moment_key for moment_key in action_result.next_moments if self._parent_adventure.is_adventure_moment_available(moment_key)))
                self._parent_adventure.queue_adventure_moment(next_moment_key)
            if action_result.results_dialog:
                dialog = action_result.results_dialog(self._sim, resolver=self.resolver)
                dialog.show_dialog()
            event_manager = services.get_event_manager()
            for event_type in action_result.events_to_send:
                event_manager.process_event(event_type, sim_info=self._sim.sim_info)
            if action_result.continuation:
                self._interaction.push_tunable_continuation(action_result.continuation)

    def _is_cheat_response(self, response):
        cheat_response = response - len(self._finish_actions)
        if cheat_response < 0:
            return False
        return True

    def _on_dialog_response(self, dialog):
        response_index = dialog.response
        if response_index is None:
            return
        if False and self._is_cheat_response(response_index):
            self._run_action_from_cheat(response_index)
            return
        if response_index >= len(self._finish_actions):
            return
        self._run_action_from_index(response_index)

    def _get_action_display_text(self, action):
        display_name = self._interaction.create_localized_string(action.display_text)
        if action.cost is not None:
            if action.cost.cost_type == self.COST_TYPE_SIMOLEONS:
                amount = action.cost.amount
                display_name = self._interaction.SIMOLEON_COST_NAME_FACTORY(display_name, amount)
            elif action.cost.cost_type == self.COST_TYPE_ITEMS:
                item_cost = action.cost.item_cost
                display_name = item_cost.get_interaction_name(self._interaction, display_name)
        return lambda *_, **__: display_name

    def _get_dialog(self):
        resolver = self.resolver
        dialog = self._visibility(self._sim, resolver)
        responses = []
        for (action_id, finish_action) in enumerate(self._finish_actions):
            result = finish_action.availability_tests.run_tests(resolver)
            if not result:
                if finish_action.disabled_text is not None:
                    disabled_text = finish_action.disabled_text if not result else None
                    responses.append(UiDialogResponse(dialog_response_id=action_id, text=self._get_action_display_text(finish_action), subtext=self._interaction.create_localized_string(finish_action.display_subtext), disabled_text=disabled_text() if disabled_text is not None else None))
            disabled_text = finish_action.disabled_text if not result else None
            responses.append(UiDialogResponse(dialog_response_id=action_id, text=self._get_action_display_text(finish_action), subtext=self._interaction.create_localized_string(finish_action.display_subtext), disabled_text=disabled_text() if disabled_text is not None else None))
        if not responses:
            return
        if False and _show_all_adventure_moments:
            responses.extend(self._parent_adventure.get_cheat_responses(action_id))
        dialog.set_responses(responses)
        dialog.add_listener(self._on_dialog_response)
        return dialog
class CreateCarriedObjectSuperInteraction(SuperInteraction):
    __qualname__ = 'CreateCarriedObjectSuperInteraction'
    INSTANCE_TUNABLES = {
        'definition':
        TunableReference(
            description='\n            The object to create.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            manager=services.definition_manager()),
        'carry_track_override':
        OptionalTunable(
            description=
            '\n            If enabled, specify which carry track the Sim must use to carry the\n            created object.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableEnumEntry(
                description=
                '\n                Which hand to carry the object in.\n                ',
                tunable_type=PostureTrack,
                default=PostureTrack.RIGHT)),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the finished object as soon as it is\n            created.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableStateValueReference()),
        'continuation':
        SuperInteraction.TunableReference(
            description=
            '\n            An interaction to push as a continuation to the carry.\n            '
        ),
        'continuation_with_affordance_overrides':
        OptionalTunable(
            description=
            "\n            If enabled, allows you to specify a continuation to the\n            carry based on a participant's object definition.\n            This continuation will be pushed in addition to the tunable continuation,\n            although you will rarely need to tune both at the same time.\n            ",
            tunable=TunableTuple(
                continuation=TunableContinuation(
                    description=
                    '\n                    A tunable continuation to push based on the parameters provided.\n                    '
                ),
                participant=TunableEnumEntry(
                    description=
                    '\n                    When using the affordance_override mapping, this\n                    is the participant we will use to get the definition.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.PickedObject),
                affordance_override=TunableMapping(
                    description=
                    "\n                    Based on the participants's object definition, you can override\n                    the affordance on the tunable continuation.\n                    ",
                    key_type=TunableReference(
                        description=
                        '\n                        The object definition to look for.\n                        ',
                        manager=services.definition_manager()),
                    value_type=SuperInteraction.TunableReference())),
            tuning_group=GroupNames.CREATE_CARRYABLE)
    }

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

    @property
    def create_target(self):
        return self.definition

    @property
    def created_target(self):
        if self._object_create_helper is not None:
            return self._object_create_helper.object

    def _get_create_continuation_affordance(self):
        def create_continuation_affordance():
            context = self.context.clone_for_continuation(self)
            aop = AffordanceObjectPair(self.continuation, self.created_target,
                                       self.continuation, None)
            return (aop, context)

        if self.continuation is not None:
            return create_continuation_affordance

    def build_basic_content(self, sequence, **kwargs):
        super_build_basic_content = super().build_basic_content

        def setup_object(obj):
            for initial_state in reversed(self.initial_states):
                obj.set_state(initial_state.state, initial_state)
            obj.set_household_owner_id(self.sim.household.id)

        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self.definition,
            self,
            init=setup_object,
            tag='CreateCarriedObjectSuperInteraction')

        def claim_object(*_, **__):
            self._object_create_helper.claim()

        def set_carry_target(_):
            if self.carry_track_override:
                self.track = self.carry_track_override
            else:
                self.track = DEFAULT
            if self.track is None:
                return False
            self.context.carry_target = self.created_target

        def push_tunable_continuation_with_affordance_overrides(_):
            if self.continuation_with_affordance_overrides is None:
                return
            obj = self.get_participant(
                self.continuation_with_affordance_overrides.participant)
            if obj is not None:
                affordance_override = self.continuation_with_affordance_overrides.affordance_override.get(
                    obj.definition)
            else:
                affordance_override = None
            interaction_parameters = {}
            if 'picked_item_ids' in self.interaction_parameters:
                interaction_parameters[
                    'picked_item_ids'] = self.interaction_parameters[
                        'picked_item_ids']
            self.push_tunable_continuation(
                self.continuation_with_affordance_overrides.continuation,
                affordance_override=affordance_override,
                **interaction_parameters)

        def enter_carry(timeline):
            result = yield element_utils.run_child(
                timeline,
                enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=claim_object,
                    create_si_fn=self._get_create_continuation_affordance(),
                    track=self.track,
                    sequence=build_critical_section(
                        super_build_basic_content(sequence, **kwargs),
                        flush_all_animations)))
            return result

        return (self._object_create_helper.create(
            set_carry_target, enter_carry,
            push_tunable_continuation_with_affordance_overrides),
                lambda _: self._object_create_helper.claimed)
class CareerPickerSuperInteraction(PickerSingleChoiceSuperInteraction):
    __qualname__ = 'CareerPickerSuperInteraction'

    class CareerPickerFilter(HasTunableSingletonFactory, AutoFactoryInit):
        __qualname__ = 'CareerPickerSuperInteraction.CareerPickerFilter'

        def is_valid(self, career):
            raise NotImplementedError

    class CareerPickerFilterAll(CareerPickerFilter):
        __qualname__ = 'CareerPickerSuperInteraction.CareerPickerFilterAll'

        def is_valid(self, career):
            return True

    class CareerPickerFilterWhitelist(CareerPickerFilter):
        __qualname__ = 'CareerPickerSuperInteraction.CareerPickerFilterWhitelist'
        FACTORY_TUNABLES = {
            'whitelist':
            TunableEnumSet(
                description=
                '\n                Only careers of this category are allowed. If this set is\n                empty, then no careers are allowed.\n                ',
                enum_type=CareerCategory)
        }

        def is_valid(self, career):
            return career.career_category in self.whitelist

    class CareerPickerFilterBlacklist(CareerPickerFilter):
        __qualname__ = 'CareerPickerSuperInteraction.CareerPickerFilterBlacklist'
        FACTORY_TUNABLES = {
            'blacklist':
            TunableEnumSet(
                description=
                '\n                Careers of this category are not allowed. All others are\n                allowed.\n                ',
                enum_type=CareerCategory)
        }

        def is_valid(self, career):
            return career.career_category not in self.blacklist

    INSTANCE_TUNABLES = {
        'continuation':
        OptionalTunable(
            description=
            '\n            If enabled, you can tune a continuation to be pushed. PickedItemId\n            will be the id of the selected career.\n            ',
            tunable=TunableContinuation(
                description=
                '\n                If specified, a continuation to push with the chosen career.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'career_filter':
        TunableVariant(
            description=
            '\n            Which career types to show.\n            ',
            all=CareerPickerFilterAll.TunableFactory(),
            blacklist=CareerPickerFilterBlacklist.TunableFactory(),
            whitelist=CareerPickerFilterWhitelist.TunableFactory(),
            default='all',
            tuning_group=GroupNames.PICKERTUNING)
    }

    @classmethod
    def _valid_careers_gen(cls, sim):
        yield (career
               for career in sim.sim_info.career_tracker.careers.values()
               if cls.career_filter.is_valid(career))

    @classmethod
    def has_valid_choice(cls, target, context, **kwargs):
        return any(cls._valid_careers_gen(context.sim))

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

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        for career in cls._valid_careers_gen(context.sim):
            track = career.current_track_tuning
            row = ObjectPickerRow(name=track.career_name(context.sim),
                                  icon=track.icon,
                                  row_description=track.career_description,
                                  tag=career)
            yield row

    def on_choice_selected(self, choice_tag, **kwargs):
        career = choice_tag
        if career is not None and self.continuation is not None:
            picked_item_set = set()
            picked_item_set.add(career.guid64)
            self.interaction_parameters['picked_item_ids'] = picked_item_set
            self.push_tunable_continuation(self.continuation,
                                           picked_item_ids=picked_item_set)
Пример #10
0
class _AdoptionActionPushContinuation(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'continuation': TunableContinuation(description='\n            A continuation that is pushed when the acting Sim is selected.\n            ', class_restrictions=('AdoptionSuperInteraction',), locked_args={'actor': ParticipantType.Actor})}

    def __call__(self, interaction, picked_sim_ids):
        interaction.interaction_parameters['picked_item_ids'] = frozenset(picked_sim_ids)
        interaction.push_tunable_continuation(self.continuation, picked_item_ids=picked_sim_ids)
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)
Пример #12
0
class AdoptionPickerInteraction(SuperInteraction, PickerSuperInteractionMixin):
    __qualname__ = 'AdoptionPickerInteraction'
    INSTANCE_TUNABLES = {
        'picker_dialog':
        TunablePickerDialogVariant(
            description='\n                Sim Picker Dialog\n                ',
            available_picker_flags=ObjectPickerTuningFlags.SIM,
            tuning_group=GroupNames.PICKERTUNING),
        'sim_filters':
        TunableList(
            description=
            "\n                A list of tuples of number of sims to find and filter to find\n                them.  If there aren't enough sims to be found from a filter\n                then the filter is used to create the sims.  Sims that are\n                found from one filter are placed into the black list for\n                running the next filter in order to make sure that sims don't\n                double dip creating one filter to the next.\n                ",
            tunable=TunableTuple(
                number_of_sims=TunableRange(
                    description=
                    '\n                        The number of sims to find using the filter.  If no\n                        sims are found then sims will be created to fit the\n                        filter.\n                        ',
                    tunable_type=int,
                    default=1,
                    minimum=1),
                filter=TunableSimFilter.TunableReference(
                    description=
                    '\n                        Sim filter that is used to create find the number of\n                        sims that we need for this filter request.\n                        '
                ),
                description=
                '\n                    Tuple of number of sims that we want to find and filter\n                    that will be used to find them.\n                    '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'actor_continuation':
        TunableContinuation(
            description=
            '\n                A continuation that is pushed when the acting sim is selected.\n                ',
            locked_args={'actor': ParticipantType.Actor},
            tuning_group=GroupNames.PICKERTUNING)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(
            choice_enumeration_strategy=SimPickerEnumerationStrategy(),
            *args,
            **kwargs)
        self._picked_sim_id = None
        self.sim_ids = []

    def _run_interaction_gen(self, timeline):
        yield self._get_valid_choices_gen(timeline)
        self._show_picker_dialog(self.sim,
                                 target_sim=self.sim,
                                 target=self.target)
        yield element_utils.run_child(timeline,
                                      element_utils.soft_sleep_forever())
        if self._picked_sim_id is None:
            self.remove_liability(PaymentLiability.LIABILITY_TOKEN)
            return False
        picked_item_set = {self._picked_sim_id}
        self.interaction_parameters['picked_item_ids'] = frozenset(
            picked_item_set)
        self.push_tunable_continuation(self.actor_continuation,
                                       picked_item_ids=picked_item_set)
        return True

    def _get_valid_choices_gen(self, timeline):
        self.sim_ids = []
        requesting_sim_info = self.sim.sim_info
        blacklist = {
            sim_info.id
            for sim_info in services.sim_info_manager().instanced_sims_gen(
                allow_hidden_flags=ALL_HIDDEN_REASONS)
        }
        for sim_filter in self.sim_filters:
            for _ in range(sim_filter.number_of_sims):
                sim_infos = services.sim_filter_service(
                ).submit_matching_filter(
                    1,
                    sim_filter.filter,
                    None,
                    blacklist_sim_ids=blacklist,
                    requesting_sim_info=requesting_sim_info,
                    allow_yielding=False,
                    zone_id=0)
                for sim_info in sim_infos:
                    self.sim_ids.append(sim_info.id)
                    blacklist.add(sim_info.id)
                yield element_utils.run_child(
                    timeline, element_utils.sleep_until_next_tick_element())

    @flexmethod
    def create_row(cls, inst, tag):
        return SimPickerRow(sim_id=tag, tag=tag)

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        if inst is not None:
            for sim_id in inst.sim_ids:
                logger.info('AdoptionPicker: add sim_id:{}', sim_id)
                row = inst.create_row(sim_id)
                yield row

    def _pre_perform(self, *args, **kwargs):
        if self.sim.household.free_slot_count == 0:
            self.cancel(
                FinishingType.FAILED_TESTS,
                cancel_reason_msg="There aren't any free household slots.")
            return
        self.add_liability(ADOPTION_LIABILTIY, AdoptionLiability())
        return super()._pre_perform(*args, **kwargs)

    def on_choice_selected(self, choice_tag, **kwargs):
        sim_id = choice_tag
        if sim_id is not None:
            self._picked_sim_id = sim_id
            self.add_liability(
                SIM_FILTER_GLOBAL_BLACKLIST_LIABILITY,
                SimFilterGlobalBlacklistLiability(
                    (sim_id, ), SimFilterGlobalBlacklistReason.ADOPTION))
        self.trigger_soft_stop()
class ApartmentPickerInteraction(TravelMixin, TerrainInteractionMixin,
                                 PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'picker_dialog':
        UiApartmentPicker.TunableFactory(
            description=
            '\n            The apartment picker dialog.\n            ',
            tuning_group=GroupNames.PICKERTUNING,
            locked_args={
                'text_cancel': None,
                'text_ok': None,
                'title': None,
                'text': None,
                'text_tokens': DEFAULT,
                'icon': None,
                'secondary_icon': None,
                'phone_ring_type': PhoneRingType.NO_RING
            }),
        '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),
        'target_continuation':
        TunableContinuation(
            description=
            '\n            If specified, a continuation to push on the sim targeted\n            ',
            tuning_group=GroupNames.PICKERTUNING)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            choice_enumeration_strategy=LotPickerEnumerationStrategy(),
            **kwargs)

    @classmethod
    def _test(cls, target, context, **kwargs):
        to_zone_id = context.pick.get_zone_id_from_pick_location()
        if not services.get_plex_service().is_zone_an_apartment(
                to_zone_id, consider_penthouse_an_apartment=False):
            return TestResult(False, 'Picked zone is not a plex.')
        return cls.travel_pick_info_test(target, context, **kwargs)

    @classmethod
    def has_valid_choice(cls, target, context, **kwargs):
        if cls._get_valid_lot_choices(target, context):
            return True
        return False

    def _push_continuations(self, zone_datas):
        picked_zone_set = {
            zone_data.zone_id
            for zone_data in zone_datas if zone_data is not None
        }
        self.interaction_parameters['picked_zone_ids'] = frozenset(
            picked_zone_set)
        insert_strategy = QueueInsertStrategy.LAST if not self.target_continuation else QueueInsertStrategy.NEXT
        if self.actor_continuation:
            self.push_tunable_continuation(self.actor_continuation,
                                           insert_strategy=insert_strategy,
                                           picked_zone_ids=picked_zone_set)
        if self.target_continuation:
            self.push_tunable_continuation(self.target_continuation,
                                           insert_strategy=insert_strategy,
                                           actor=self.target,
                                           picked_zone_ids=picked_zone_set)

    def _create_dialog(self, owner, target_sim=None, target=None, **kwargs):
        traveling_sims = []
        picked_sims = self.get_participants(ParticipantType.PickedSim)
        if picked_sims:
            traveling_sims = list(picked_sims)
        elif target is not None and target.is_sim and target is not self.sim:
            traveling_sims.append(target)
        dialog = self.picker_dialog(owner,
                                    title=lambda *_, **__: self.get_name(),
                                    resolver=self.get_resolver(),
                                    traveling_sims=traveling_sims)
        self._setup_dialog(dialog, **kwargs)
        dialog.set_target_sim(target_sim)
        dialog.set_target(target)
        dialog.add_listener(self._on_picker_selected)
        return dialog

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

    @flexmethod
    def create_row(cls, inst, tag):
        return LotPickerRow(zone_data=tag, option_id=tag.zone_id, tag=tag)

    @flexmethod
    def _get_valid_lot_choices(cls, inst, target, context, target_list=None):
        to_zone_id = context.pick.get_zone_id_from_pick_location()
        if to_zone_id is None:
            logger.error(
                'Could not resolve lot id: {} into a valid zone id when traveling to an apartment lot.',
                context.pick.lot_id,
                owner='rmccord')
            return []
        plex_service = services.get_plex_service()
        if not plex_service.is_zone_a_plex(to_zone_id):
            return []
        valid_zone_ids = plex_service.get_plex_zones_in_group(to_zone_id)
        persistence_service = services.get_persistence_service()
        results = list(
            persistence_service.get_zone_proto_buff(zone_id)
            for zone_id in valid_zone_ids)
        return results

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        for zone_data in inst_or_cls._get_valid_lot_choices(target, context):
            logger.info('ApartmentPicker: add zone_data:{}', zone_data)
            yield LotPickerRow(zone_data=zone_data,
                               option_id=zone_data.zone_id,
                               tag=zone_data)

    def _on_picker_selected(self, dialog):
        results = dialog.get_result_tags()
        if results:
            self._push_continuations(results)

    def on_choice_selected(self, choice_tag, **kwargs):
        result = choice_tag
        if result is not None:
            self._push_continuations((result, ))
Пример #14
0
class RecordTrendsElement(elements.ParentElement, ObjectCreationMixin,
                          HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'subject':
        TunableEnumEntry(
            description=
            '\n            The subject we want to record trends from.\n            ',
            tunable_type=ParticipantTypeSingleSim,
            default=ParticipantTypeSingleSim.Actor),
        'continuation':
        TunableContinuation(
            description=
            '\n            The continuation to push if we recorded a trend.\n            '
        ),
        'celebrity_tests':
        OptionalTunable(
            description=
            '\n            If enabled, we will run these tests and attempt to apply the\n            celebrity trend if they pass.\n            ',
            tunable=TunableTestSet(
                description=
                '\n                The tests to determine whether or not we should apply the celebrity\n                trend to the video recorded by this interaction.\n                '
            )),
        'locked_args': {
            'creation_data': None
        }
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(*args, **kwargs)
        self.interaction = interaction
        self.creation_data = _TrendsCreationData()
        self.sequence = sequence
        self.resolver = interaction.get_resolver()
        self._recorded_sim = None
        self._registered_events = []

    def unregister_trend_events(self):
        if self._registered_events:
            event_manager = services.get_event_manager()
            event_manager.unregister(self, self._registered_events)
            self._registered_events.clear()

    def handle_event(self, sim_info, event_type, resolver, *_, **__):
        if self.creation_data.has_recorded_trend_tag or sim_info.sim_id != self._recorded_sim.id:
            return
        if event_type == TestEvent.SkillValueChange:
            skill = resolver.event_kwargs['skill']
            if skill.trend_tag is not None:
                self.creation_data.record_trend_tag(skill.trend_tag)
        if self.creation_data.has_recorded_trend_tag:
            self.unregister_trend_events()

    def _record_static_trends(self):
        if self.celebrity_tests is not None and self.celebrity_tests.run_tests(
                self.resolver):
            self.creation_data.record_trend_tag(TrendTuning.CELEBRITY_TREND)
            return
        elif self._recorded_sim.age == Age.CHILD or self._recorded_sim.age == Age.TODDLER:
            self.creation_data.record_trend_tag(
                TrendTuning.TODDLER_CHILD_TREND)
            return

    def _start_recording(self, _):
        self._recorded_sim = self.interaction.get_participant(self.subject)
        if self._recorded_sim is None:
            logger.error('Subject is None for {} on {}.', self.subject,
                         self.interaction)
        self._record_static_trends()
        if not self.creation_data.has_recorded_trend_tag:
            event_manager = services.get_event_manager()
            event_manager.register_single_event(self,
                                                TestEvent.SkillValueChange)
            self._registered_events.append(TestEvent.SkillValueChange)

    def _stop_recording(self, _):
        if services.current_zone().is_zone_shutting_down:
            return
        self.unregister_trend_events()
        created_object = self.create_object(self.resolver)
        if created_object is None:
            logger.error('Failed to create trend recording {} on {}',
                         self.creation_data.recorded_trend_tag,
                         self.interaction)
            return
        self.interaction.context.create_target_override = created_object
        self.interaction.push_tunable_continuation(self.continuation)

    def _run(self, timeline):
        sequence = [self._start_recording, self.sequence, self._stop_recording]
        child_element = build_element(sequence,
                                      critical=CleanupType.OnCancelOrException)
        return timeline.run_child(child_element)
Пример #15
0
class SkillPickerSuperInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        '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),
        'show_hidden_skills':
        Tunable(
            description=
            ' \n                When true, shows hidden skills in the picker.\n                ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.PICKERTUNING),
        'show_max_level_skills':
        Tunable(
            description=
            '\n                When true, will allow skills at max level to be shown in the picker\n                ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.PICKERTUNING),
        'show_unattained_skills':
        Tunable(
            description=
            "\n                When true, will allow skills that the Sim doesn't have at any level to appear.\n                NOTE: If this is true, skill_range_filter will be ignored for skills the Sim does not have\n                already.\n                ",
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.PICKERTUNING),
        'skill_range_filter':
        OptionalTunable(tunable=TunableInterval(
            description=
            '\n                       A skill must fall within the given range for the actor for that\n                       skill to show up in the picker\n                       ',
            tunable_type=int,
            default_lower=0,
            default_upper=20),
                        tuning_group=GroupNames.PICKERTUNING),
        'stat_to_copy_value_to':
        OptionalTunable(tunable=TunableReference(
            description=
            '\n                    If tuned, the value of the picked statistic will be copied to this stat\n                    after picking.\n                    ',
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC),
            class_restrictions=('Skill', )),
                        tuning_group=GroupNames.PICKERTUNING)
    }

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

    @classmethod
    def _is_skill_valid(cls, skill, sim):
        if not skill.can_add(sim):
            return False
        if skill.hidden and not cls.show_hidden_skills:
            return False
        tracker = sim.get_tracker(skill)
        if tracker is None:
            return False
        stat = tracker.get_statistic(skill)
        if stat is None and not cls.show_unattained_skills:
            return False
        if stat is not None:
            if not cls.show_max_level_skills and stat.reached_max_level:
                return False
            elif cls.skill_range_filter:
                skill_value = stat.get_user_value()
                if skill_value < cls.skill_range_filter.lower_bound or skill_value > cls.skill_range_filter.upper_bound:
                    return False
        return True

    @classmethod
    def has_valid_choice(cls, target, context, **kwargs):
        sim = context.sim
        skill_manager = services.get_instance_manager(
            sims4.resources.Types.STATISTIC)
        for skill in skill_manager.get_ordered_types(only_subclasses_of=Skill):
            if cls._is_skill_valid(skill, sim):
                return True
        return False

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        sim = context.sim
        skill_manager = services.get_instance_manager(
            sims4.resources.Types.STATISTIC)
        for skill in skill_manager.get_ordered_types(only_subclasses_of=Skill):
            if not cls._is_skill_valid(skill, sim):
                continue
            row = ObjectPickerRow(name=skill.stat_name,
                                  icon=skill.icon,
                                  row_description=skill.skill_description,
                                  tag=skill)
            yield row

    def on_choice_selected(self, choice_tag, **kwargs):
        if choice_tag is not None:
            if self.stat_to_copy_value_to is not None:
                sim = self._sim
                tracker = sim.get_tracker(choice_tag)
                if tracker is not None:
                    stat = tracker.get_statistic(choice_tag)
                    if stat is not None:
                        tracker.set_value(self.stat_to_copy_value_to,
                                          stat.get_value(),
                                          add=True)
            kwargs_copy = kwargs.copy()
            kwargs_copy['picked_statistic'] = choice_tag
            self.push_tunable_continuation(self.actor_continuation,
                                           **kwargs_copy)
Пример #16
0
 def __init__(self, animation_callback=DEFAULT, allow_multi_si_cancel=False, allow_social_animation=False, locked_args=None, **kwargs):
     import interactions.base.basic
     animation_ref = TunableAnimationReference(callback=animation_callback, interaction_asm_type=InteractionAsmType.Outcome, description='The one-shot animation ref to play')
     animation_ref = OptionalTunable(animation_ref)
     if allow_multi_si_cancel:
         cancel_si = OptionalTunable(TunableEnumFlags(description='\n                                                                     Every participant in this list will have the SI\n                                                                     associated with the interaction owning this outcome\n                                                                     canceled.\n                                                                     ', enum_type=ParticipantType))
     else:
         cancel_si = TunableVariant(description="\n                                        When enabled, the outcome will cancel the owning\n                                        interaction's parent SI.\n                                        ", locked_args={'enabled': ParticipantType.Actor, 'disabled': None}, default='disabled')
     if allow_social_animation:
         kwargs['social_animation'] = OptionalTunable(description='\n                If enabled, specify an animation that will be played once for\n                the entire social group.\n                ', tunable=TunableAnimationReference())
     else:
         locked_args = {} if locked_args is None else locked_args
         locked_args['social_animation'] = None
     if locked_args is None:
         locked_args = {TunableOutcomeActions.ADD_TARGET_AFFORDANCE_LOOT_KEY: True}
     elif TunableOutcomeActions.ADD_TARGET_AFFORDANCE_LOOT_KEY not in locked_args:
         locked_args.update({TunableOutcomeActions.ADD_TARGET_AFFORDANCE_LOOT_KEY: True})
     super().__init__(animation_ref=animation_ref, xevt=OptionalTunable(description='\n                             When specified, the outcome will be associated to this xevent.\n                             ', tunable=Tunable(tunable_type=int, default=None)), response=OptionalTunable(TunableResponseSelector()), force_outcome_on_exit=Tunable(description='\n                             If checked outcome will always be given even if\n                             interaction was canceled. If unchecked, outcome\n                             will only be given if mixer, one_shot, or\n                             naturally finishing interaction.\n                             ', tunable_type=bool, default=False), loot_list=TunableList(description='\n                             A list of pre-defined loot operations.\n                             ', tunable=LootActions.TunableReference()), consume_object=Tunable(description="\n                             If checked, the loot list generated in the target\n                             object's consumable component will be added to\n                             this outcome's loot list.\n                             ", tunable_type=bool, default=False), continuation=TunableContinuation(description='An affordance to be pushed as part of an outcome.'), cancel_si=cancel_si, events_to_send=TunableList(TunableEnumEntry(TestEvent, TestEvent.Invalid, description='events types to send')), display_message=OptionalTunable(description='\n                             If set, flyaway text will be shown.\n                             ', tunable=TunableLocalizedString(default=None, description='Localized string that is shown as flyaway text')), loot_offset_override=OptionalTunable(Tunable(int, 0), description="An override to interactions.utils.loot's DEFAULT_LOOT_OFFSET tuning, specifically for this outcome."), parameterized_autonomy=TunableMapping(description='\n                             Specify parameterized autonomy for the participants of the interaction.\n                             ', key_type=TunableEnumEntry(description='\n                                 The participant to run parameterized autonomy for.\n                                 ', tunable_type=ParticipantType, default=ParticipantType.Actor), value_type=TunableList(description='\n                                 A list of parameterized autonomy requests to run.\n                                 ', tunable=TunableParameterizedAutonomy())), outcome_result=TunableEnumEntry(description='\n                             The interaction outcome result to consider this interaction result. This is\n                             important for testing interactions, such as an aspiration that wants to know\n                             if a Sim has successfully kissed another Sim. All interactions that a designer\n                             would consider a success case for such a scenario would be assured here.\n                             ', tunable_type=OutcomeResult, default=OutcomeResult.SUCCESS), basic_extras=interactions.base.basic.TunableBasicExtrasCore(), locked_args=locked_args, **kwargs)
Пример #17
0
class AdventureMoment(HasTunableFactory, AutoFactoryInit):
    __qualname__ = 'AdventureMoment'
    LOOT_NOTIFICATION_TEXT = TunableLocalizedStringFactory(description='\n        A string used to recursively build loot notification text. It will be\n        given two tokens: a loot display text string, if any, and the previously\n        built LOOT_NOTIFICATION_TEXT string.\n        ')
    NOTIFICATION_TEXT = TunableLocalizedStringFactory(description='\n        A string used to format notifications. It will be given two arguments:\n        the notification text and the built version of LOOT_NOTIFICATION_TEXT,\n        if not empty.\n        ')
    COST_TYPE_SIMOLEONS = 0
    COST_TYPE_ITEMS = 1
    FACTORY_TUNABLES = {'description': '\n            A phase of an adventure. Adventure moments may present\n            some information in a dialog form and for a choice to be\n            made regarding how the overall adventure will branch.\n            ', '_visibility': OptionalTunable(description='\n            Control whether or not this moment provides visual feedback to\n            the player (i.e., a modal dialog).\n            ', tunable=UiDialog.TunableFactory(), disabled_name='not_visible', enabled_name='show_dialog'), '_finish_actions': TunableList(description='\n            A list of choices that can be made by the player to determine\n            branching for the adventure. At most two finish actions can\n            be tuned. They will be displayed as buttons in the UI. If no\n            dialog is displayed, then the first finish action will be selected.\n            If this list is empty, the adventure ends.\n            ', tunable=TunableTuple(display_text=TunableLocalizedStringFactoryVariant(description="\n                   This finish action's title. This will be the button text in\n                   the UI.\n                   "), cost=TunableVariant(description='\n                    The cost associated with this finish action. Only one type\n                    of cost may be tuned. The player is informed of the cost\n                    before making the selection by modifying the display_text\n                    string to include this information.\n                    ', simoleon_cost=TunableTuple(description="The specified\n                        amount will be deducted from the Sim's funds.\n                        ", locked_args={'cost_type': COST_TYPE_SIMOLEONS}, amount=TunableRange(description='How many Simoleons to\n                            deduct.\n                            ', tunable_type=int, default=0, minimum=0)), item_cost=TunableTuple(description="The specified items will \n                        be removed from the Sim's inventory.\n                        ", locked_args={'cost_type': COST_TYPE_ITEMS}, item_cost=ItemCost.TunableFactory()), default=None), action_results=TunableList(description='\n                    A list of possible results that can occur if this finish\n                    action is selected. Action results can award loot, display\n                    notifications, and control the branching of the adventure by\n                    selecting the next adventure moment to run.\n                    ', tunable=TunableTuple(weight_modifiers=TunableList(description='\n                            A list of modifiers that affect the probability that\n                            this action result will be chosen. These are exposed\n                            in the form (test, multiplier). If the test passes,\n                            then the multiplier is applied to the running total.\n                            The default multiplier is 1. To increase the\n                            likelihood of this action result being chosen, tune\n                            multiplier greater than 1. To decrease the\n                            likelihood of this action result being chose, tune\n                            multipliers lower than 1. If you want to exclude\n                            this action result from consideration, tune a\n                            multiplier of 0.\n                            ', tunable=TunableTuple(description='\n                                A pair of test and weight multiplier. If the\n                                test passes, the associated weight multiplier is\n                                applied. If no test is specified, the multiplier\n                                is always applied.\n                                ', test=TunableTestVariant(description='\n                                    The test that has to pass for this weight\n                                    multiplier to be applied. The information\n                                    available to this test is the same\n                                    information available to the interaction\n                                    owning this adventure.\n                                    ', test_locked_args={'tooltip': None}), weight_multiplier=Tunable(description='\n                                    The weight multiplier to apply if the\n                                    associated test passes.\n                                    ', tunable_type=float, default=1))), notification=OptionalTunable(description='\n                            If set, this notification will be displayed.\n                            ', tunable=TunableUiDialogNotificationSnippet()), next_moments=TunableList(description='\n                            A list of adventure moment keys. One of these keys will\n                            be selected to determine which adventure moment is\n                            selected next. If the list is empty, the adventure ends\n                            here. Any of the keys tuned here will have to be tuned\n                            in the _adventure_moments tunable for the owning adventure.\n                            ', tunable=AdventureMomentKey), loot_actions=TunableList(description='\n                            List of Loot actions that are awarded if this action result is selected.\n                            ', tunable=LootActions.TunableReference()), continuation=TunableContinuation(description='\n                            A continuation to push when running finish actions.\n                            ')))), maxlength=2)}

    def __init__(self, parent_adventure, **kwargs):
        super().__init__(**kwargs)
        self._parent_adventure = parent_adventure

    @property
    def _interaction(self):
        return self._parent_adventure.interaction

    @property
    def _sim(self):
        return self._interaction.sim

    def run_adventure(self):
        if self._visibility is None:
            self._run_action_from_index(0)
        else:
            dialog = self._get_dialog()
            dialog.show_dialog()

    def _run_action_from_index(self, action_index):
        action = self._finish_actions[action_index]
        if self._apply_action_cost(action):
            self._run_finish_actions(action)

    def _get_action_result_weight(self, action_result):
        interaction_resolver = self._interaction.get_resolver()
        weight = 1
        for modifier in action_result.weight_modifiers:
            while modifier.test is None or interaction_resolver(modifier.test):
                weight *= modifier.weight_multiplier
        return weight

    def _apply_action_cost(self, action):
        if action.cost is not None:
            if action.cost.cost_type == self.COST_TYPE_SIMOLEONS:
                amount = action.cost.amount
                if amount > self._sim.family_funds.money:
                    return False
                self._sim.family_funds.remove(amount, Consts_pb2.TELEMETRY_INTERACTION_COST, sim=self._sim)
            elif action.cost.cost_type == self.COST_TYPE_ITEMS:
                item_cost = action.cost.item_cost
                return item_cost.consume_interaction_cost(self._interaction)()
        return True

    def _run_finish_actions(self, finish_action):
        weight_pairs = [(self._get_action_result_weight(action_result), action_result) for action_result in finish_action.action_results]
        action_result = weighted_random_item(weight_pairs)
        if action_result is not None:
            loot_display_text = None
            resolver = self._interaction.get_resolver()
            for actions in action_result.loot_actions:
                for (loot_op, test_ran) in actions.get_loot_ops_gen(resolver):
                    while loot_op.apply_to_resolver(resolver, skip_test=test_ran):
                        if action_result.notification is not None:
                            current_loot_display_text = loot_op.get_display_text()
                            if current_loot_display_text is not None:
                                if loot_display_text is None:
                                    loot_display_text = current_loot_display_text
                                else:
                                    loot_display_text = self.LOOT_NOTIFICATION_TEXT(loot_display_text, current_loot_display_text)
            if action_result.notification is not None:
                if loot_display_text is not None:
                    notification_text = lambda *tokens: self.NOTIFICATION_TEXT(action_result.notification.text(*tokens), loot_display_text)
                else:
                    notification_text = action_result.notification.text
                dialog = action_result.notification(self._sim, self._interaction.get_resolver())
                dialog.text = notification_text
                dialog.show_dialog()
            if action_result.next_moments:
                next_moment_key = random.choice(action_result.next_moments)
                self._parent_adventure.queue_adventure_moment(next_moment_key)
            if action_result.continuation:
                self._interaction.push_tunable_continuation(action_result.continuation)

    def _on_dialog_response(self, dialog):
        if dialog.response is not None:
            self._run_action_from_index(dialog.response)

    def _get_action_display_text(self, action):
        display_name = self._interaction.create_localized_string(action.display_text)
        if action.cost is not None:
            if action.cost.cost_type == self.COST_TYPE_SIMOLEONS:
                amount = action.cost.amount
                display_name = self._interaction.SIMOLEON_COST_NAME_FACTORY(display_name, amount)
            elif action.cost.cost_type == self.COST_TYPE_ITEMS:
                item_cost = action.cost.item_cost
                display_name = item_cost.get_interaction_name(self._interaction, display_name)
        return lambda *_, **__: display_name

    def _get_dialog(self):
        dialog = self._visibility(self._sim, self._interaction.get_resolver())
        dialog.set_responses(tuple(UiDialogResponse(dialog_response_id=i, text=self._get_action_display_text(action)) for (i, action) in enumerate(self._finish_actions)))
        dialog.add_listener(self._on_dialog_response)
        return dialog
Пример #18
0
class CareerPickerSuperInteraction(PickerSingleChoiceSuperInteraction):
    class CareerPickerFilter(HasTunableSingletonFactory, AutoFactoryInit):
        def is_valid(self, inter_cls, inter_inst, target, context, career,
                     **kwargs):
            raise NotImplementedError

    class CareerPickerFilterAll(CareerPickerFilter):
        def is_valid(self, inter_cls, inter_inst, target, context, career,
                     **kwargs):
            return True

    class CareerPickerFilterWhitelist(CareerPickerFilter):
        FACTORY_TUNABLES = {
            'whitelist':
            TunableEnumSet(
                description=
                '\n                Only careers of this category are allowed. If this set is\n                empty, then no careers are allowed.\n                ',
                enum_type=CareerCategory)
        }

        def is_valid(self, inter_cls, inter_inst, target, context, career,
                     **kwargs):
            return career.career_category in self.whitelist

    class CareerPickerFilterBlacklist(CareerPickerFilter):
        FACTORY_TUNABLES = {
            'blacklist':
            TunableEnumSet(
                description=
                '\n                Careers of this category are not allowed. All others are\n                allowed.\n                ',
                enum_type=CareerCategory)
        }

        def is_valid(self, inter_cls, inter_inst, target, context, career,
                     **kwargs):
            return career.career_category not in self.blacklist

    class CareerPickerFilterTested(CareerPickerFilter):
        FACTORY_TUNABLES = {
            'tests':
            event_testing.tests.TunableTestSet(
                description=
                '\n                A set of tests that are run against the prospective careers. At least\n                one test must pass in order for the prospective career to show. All\n                careers will pass if there are no tests. PickedItemId is the \n                participant type for the prospective career.\n                '
            )
        }

        def is_valid(self, inter_cls, inter_inst, target, context, career,
                     **kwargs):
            if inter_inst:
                interaction_parameters = inter_inst.interaction_parameters.copy(
                )
            else:
                interaction_parameters = kwargs.copy()
            interaction_parameters['picked_item_ids'] = {career.guid64}
            resolver = InteractionResolver(inter_cls,
                                           inter_inst,
                                           target=target,
                                           context=context,
                                           **interaction_parameters)
            if not self.tests.run_tests(resolver):
                return False
            return True

    INSTANCE_TUNABLES = {
        'continuation':
        OptionalTunable(
            description=
            '\n            If enabled, you can tune a continuation to be pushed. PickedItemId\n            will be the id of the selected career.\n            ',
            tunable=TunableContinuation(
                description=
                '\n                If specified, a continuation to push with the chosen career.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'career_filter':
        TunableVariant(
            description=
            '\n            Which career types to show.\n            ',
            all=CareerPickerFilterAll.TunableFactory(),
            blacklist=CareerPickerFilterBlacklist.TunableFactory(),
            whitelist=CareerPickerFilterWhitelist.TunableFactory(),
            tested=CareerPickerFilterTested.TunableFactory(),
            default='all',
            tuning_group=GroupNames.PICKERTUNING)
    }

    @flexmethod
    def _valid_careers_gen(cls, inst, target, context, **kwargs):
        sim = context.sim
        if sim is None:
            return
        yield from (career for career in sim.sim_info.careers.values()
                    if cls.career_filter.is_valid(cls, inst, target, context,
                                                  career, **kwargs))

    @classmethod
    def has_valid_choice(cls, target, context, **kwargs):
        return any(cls._valid_careers_gen(target, context, **kwargs))

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

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        for career in inst_or_cls._valid_careers_gen(target, context,
                                                     **kwargs):
            track = career.current_track_tuning
            row = ObjectPickerRow(name=track.get_career_name(context.sim),
                                  icon=track.icon,
                                  row_description=track.get_career_description(
                                      context.sim),
                                  tag=career)
            yield row

    def on_choice_selected(self, choice_tag, **kwargs):
        career = choice_tag
        if career is not None and self.continuation is not None:
            picked_item_set = set()
            picked_item_set.add(career.guid64)
            self.interaction_parameters['picked_item_ids'] = picked_item_set
            self.push_tunable_continuation(self.continuation,
                                           picked_item_ids=picked_item_set)
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)