Exemplo n.º 1
0
class LiveDragTuning:
    __qualname__ = 'LiveDragTuning'
    LIVE_DRAG_SELL_DIALOG = UiDialogOkCancel.TunableFactory(
        description=
        '\n        The dialog to show when the user tries to sell an object via Live Drag.\n        '
    )
    LIVE_DRAG_SELL_STACK_DIALOG = UiDialogOkCancel.TunableFactory(
        description=
        '\n        The dialog to show when the user tries to sell a stack via Live Drag.\n        '
    )
Exemplo n.º 2
0
class LiveDragTuning:
    LIVE_DRAG_SELL_DIALOG = UiDialogOkCancel.TunableFactory(
        description=
        '\n        The dialog to show when the user tries to sell an object via Live Drag.\n        '
    )
    LIVE_DRAG_SELL_STACK_DIALOG = UiDialogOkCancel.TunableFactory(
        description=
        '\n        The dialog to show when the user tries to sell a stack via Live Drag.\n        '
    )
    LIVE_DRAG_SELL_FAVORITE_DIALOG = UiDialogOkCancel.TunableFactory(
        description=
        '\n        The dialog to show when the user tries to sell a favorite object via Live Drag.\n        '
    )
Exemplo n.º 3
0
class _SituationTravelRequestAllow(HasTunableSingletonFactory,
                                   AutoFactoryInit):
    FACTORY_TUNABLES = {
        'dialog':
        OptionalTunable(
            description=
            '\n            If enabled, display a prompt requiring player confirmation. If\n            disabled, immediately end this situation and allow the travel\n            request to go through.\n            ',
            tunable=UiDialogOkCancel.TunableFactory())
    }

    def __call__(self, user_facing_situation, travel_situation_type,
                 travel_request_fn, **kwargs):
        if self.dialog is None:
            return travel_request_fn()

        def on_response(dialog):
            if dialog.accepted:
                travel_request_fn()

        dialog = self.dialog(services.active_sim_info())
        dialog.show_dialog(on_response=on_response)

    @property
    def restrict(self):
        return SituationTravelRequestType.ALLOW
Exemplo n.º 4
0
 def __init__(self, *args, **kwargs):
     super().__init__(
         *args,
         dialog_ok=UiDialogOk.TunableFactory(),
         dialog_ok_cancel=UiDialogOkCancel.TunableFactory(),
         dialog_icon_label=UiDialogLabeledIcons.TunableFactory(),
         default='dialog_ok',
         **kwargs)
Exemplo n.º 5
0
class UiDialogElement(HasTunableFactory, elements.ParentElement):
    FACTORY_TUNABLES = {
        'dialog':
        TunableVariant(
            description=
            '\n            The dialog to prompt the user with.\n            ',
            ok_cancel=UiDialogOkCancel.TunableFactory(),
            personality_assignment=SimPersonalityAssignmentDialog.
            TunableFactory(),
            default='ok_cancel')
    }

    def __init__(self,
                 *args,
                 dialog=None,
                 on_response=None,
                 additional_tokens=(),
                 dialog_kwargs=frozendict(),
                 **kwargs):
        super().__init__(**kwargs)
        merged_kwargs = dialog_kwargs.copy()
        merged_kwargs.update(kwargs)
        self._dialog = dialog(*args, **merged_kwargs)
        self._dialog.add_listener(self._on_response)
        self._result = None
        self._on_response = on_response
        self._additional_tokens = additional_tokens

    def _on_response(self, dialog):
        if self._dialog is None:
            return
        if self._on_response is not None:
            self._result = self._on_response(dialog)
        else:
            self._result = dialog.accepted
        self.trigger_soft_stop()

    def _run(self, timeline):
        self._dialog.show_dialog(additional_tokens=self._additional_tokens)
        if self._result is None:
            return timeline.run_child(element_utils.soft_sleep_forever())
        return self._result

    def _resume(self, timeline, child_result):
        if self._result is not None:
            return self._result
        return False

    def _hard_stop(self):
        super()._hard_stop()
        if self._dialog is not None:
            services.ui_dialog_service().dialog_cancel(self._dialog.dialog_id)
        self._dialog = None

    def _soft_stop(self):
        super()._soft_stop()
        self._dialog = None
Exemplo n.º 6
0
class _dialog_ok_cancel_and_loot(_dialog_and_loot):
    FACTORY_TUNABLES = {
        'dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The ok cancel dialog that will display to the user.\n            '
        ),
        'on_dialog_complete_loot_list':
        TunableList(
            description=
            '\n            A list of loot that will be applied when the player responds to the\n            dialog or, if the dialog is a phone ring or text message, when the\n            dialog times out due to the player ignoring it for too long.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
                                     class_restrictions=('LootActions',
                                                         'RandomWeightedLoot'),
                                     pack_safe=True)),
        'on_dialog_accetped_loot_list':
        TunableList(
            description=
            '\n            A list of loot operations to apply when the player chooses ok.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
                                     class_restrictions=('LootActions',
                                                         'RandomWeightedLoot'),
                                     pack_safe=True)),
        'on_dialog_canceled_loot_list':
        TunableList(
            description=
            '\n            A list of loot operations to apply when the player chooses cancel.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
                                     class_restrictions=('LootActions',
                                                         'RandomWeightedLoot'),
                                     pack_safe=True))
    }

    def on_node_run(self, drama_node):
        resolver = drama_node._get_resolver()
        target_sim_id = drama_node._sender_sim_info.id if drama_node._sender_sim_info is not None else None
        dialog = self.dialog(drama_node._receiver_sim_info,
                             target_sim_id=target_sim_id,
                             resolver=resolver)

        def response(dialog):
            for loot_action in self.on_dialog_complete_loot_list:
                loot_action.apply_to_resolver(resolver)
            if dialog.response is not None:
                if dialog.response == ButtonType.DIALOG_RESPONSE_OK:
                    for loot_action in self.on_dialog_accetped_loot_list:
                        loot_action.apply_to_resolver(resolver)
                elif dialog.response == ButtonType.DIALOG_RESPONSE_CANCEL:
                    for loot_action in self.on_dialog_canceled_loot_list:
                        loot_action.apply_to_resolver(resolver)
            DialogDramaNode.apply_cooldown_on_response(drama_node)

        dialog.show_dialog(on_response=response)
 def _create_dialog(self) -> Union[UiDialogOkCancel, None]:
     try:
         return UiDialogOkCancel.TunableFactory().default(
             CommonSimUtils.get_active_sim_info(),
             text=lambda *_, **__: self.description,
             title=lambda *_, **__: self.title,
             text_ok=lambda *_, **__: self.ok_text,
             text_cancel=lambda *_, **__: self.cancel_text)
     except Exception as ex:
         self.log.error('_create_dialog', exception=ex)
     return None
Exemplo n.º 8
0
class RunAffordanceDramaNode(BaseDramaNode):
    INSTANCE_TUNABLES = {
        'dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The ok cancel dialog that will display to the user.\n            '
        ),
        'affordance':
        TunableReference(
            description=
            "\n            The affordance that will be pushed on the receiving Sim once the\n            dialog is accepted.  This affordance will be pushed at high\n            priority next in the Sim's queue.  The sending Sim will be placed\n            in the picked Sims.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            class_restrictions='SuperInteraction')
    }

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

    def _push_affordance(self, dialog):
        if not dialog.accepted:
            services.drama_scheduler_service().complete_node(self.uid)
            return
        sim = self._receiver_sim_info.get_sim_instance()
        if sim is None:
            services.drama_scheduler_service().complete_node(self.uid)
            return
        context = interactions.context.InteractionContext(
            sim,
            interactions.context.InteractionContext.
            SOURCE_SCRIPT_WITH_USER_INTENT,
            interactions.priority.Priority.High,
            insert_strategy=interactions.context.QueueInsertStrategy.NEXT,
            bucket=interactions.context.InteractionBucketType.DEFAULT)
        if self._sender_sim_info is not None:
            picked_sim_ids = (self._sender_sim_info.id, )
        else:
            picked_sim_ids = tuple()
        sim.push_super_affordance(self.affordance,
                                  sim,
                                  context,
                                  picked_item_ids=picked_sim_ids)
        services.drama_scheduler_service().complete_node(self.uid)

    def _run(self):
        resolver = self._get_resolver()
        target_sim_id = self._sender_sim_info.id if self._sender_sim_info is not None else None
        dialog = self.dialog(self._receiver_sim_info,
                             target_sim_id=target_sim_id,
                             resolver=resolver)
        dialog.show_dialog(on_response=self._push_affordance)
        return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE
class MoveInSuperInteraction(ImmediateSuperInteraction):
    __qualname__ = 'MoveInSuperInteraction'
    INSTANCE_TUNABLES = {
        'dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The dialog box presented to ask if the player should move their Sims in together.'
        ),
        'situation_blacklist':
        event_testing.test_variants.TunableSituationRunningTest()
    }

    def _run_interaction_gen(self, timeline):
        services.sim_info_manager()._set_default_genealogy()
        resolver = DataResolver(self.sim.sim_info)
        if not resolver(self.situation_blacklist):
            return True
        if not self.target.is_sim:
            return True
        if self.sim.household_id == self.target.household_id:
            return True

        def on_response(dialog):
            if not dialog.accepted:
                self.cancel_user(
                    cancel_reason_msg=
                    'Move-In. Player canceled, or move in together dialog timed out from client.'
                )
                return
            actor = self.get_participant(ParticipantType.Actor)
            src_household_id = actor.sim_info.household.id
            target = self.target
            tgt_household_id = target.sim_info.household.id
            client_manager = services.client_manager()
            if client_manager is not None:
                client = client_manager.get_first_client()
                if client is not None:
                    active_sim_id = client.active_sim.id
            if src_household_id is not None and tgt_household_id is not None and active_sim_id is not None:
                transfer_info = InteractionOps_pb2.SimTransferRequest()
                transfer_info.source_household_id = src_household_id
                transfer_info.target_household_id = tgt_household_id
                transfer_info.active_sim_id = active_sim_id
                system_distributor = distributor.system.Distributor.instance()
                generic_pb_op = GenericProtocolBufferOp(
                    DistributorOps_pb2.Operation.SIM_TRANSFER_REQUEST,
                    transfer_info)
                system_distributor.add_op_with_no_owner(generic_pb_op)

        dialog = self.dialog(self.sim, self.get_resolver())
        dialog.show_dialog(on_response=on_response)
        return True
 def _create_dialog(
         self, sim_info: SimInfo,
         target_sim_info: SimInfo) -> Union[UiDialogOkCancel, None]:
     try:
         return UiDialogOkCancel.TunableFactory().default(
             sim_info,
             text=lambda *_, **__: self.description,
             text_ok=lambda *_, **__: self.ok_text,
             text_cancel=lambda *_, **__: self.cancel_text,
             target_sim_id=CommonSimUtils.get_sim_id(target_sim_info),
             resolver=DoubleSimResolver(sim_info, target_sim_info))
     except Exception as ex:
         CommonExceptionHandler.log_exception(self.mod_identity.name,
                                              '_create_dialog',
                                              exception=ex)
     return None
Exemplo n.º 11
0
class DaycareTuning:
    NANNY_SERVICE_NPC = TunableReference(description='\n        The nanny service NPC. We check if this is hired to take \n        away babies on sims leaving.\n        ', manager=services.get_instance_manager(sims4.resources.Types.SERVICE_NPC))
    BUTLER_SERVICE_NPC = TunablePackSafeReference(description='\n        The butler service NPC. If selected to look after children, the butler\n        should have similar effects as the nanny with regards to Daycare.\n        ', manager=services.get_instance_manager(sims4.resources.Types.SERVICE_NPC))
    NANNY_SERVICE_NPC_DIALOG = UiDialogOkCancel.TunableFactory(description='\n        A dialog that shows up when toddlers (not babies) are left home alone\n        requiring daycare. If the player selects Ok, a Nanny NPC is hired for\n        the duration of daycare, and the player can keep playing with their\n        toddlers. If Cancel is selected, regular daycare behavior kicks in and\n        the toddlers become uncontrollable.\n        ')
    DAYCARE_TRAIT_ON_KIDS = TunableReference(description='\n        The trait that indicates a baby is at daycare.\n        ', manager=services.trait_manager())
    NANNY_TRAIT_ON_KIDS = TunableReference(description='\n        The trait that children and babies that are with the nanny have.\n        ', manager=services.trait_manager())
    SEND_BABY_TO_DAYCARE_NOTIFICATION_SINGLE_BABY = TunableUiDialogNotificationSnippet(description='\n        The message appearing when a single baby is sent to daycare. You can\n        reference this single baby by name.\n        ')
    SEND_BABY_TO_DAYCARE_NOTIFICATION_MULTIPLE_BABIES = TunableUiDialogNotificationSnippet(description='\n        The message appearing when multiple babies are sent to daycare. You can\n        not reference any of these babies by name.\n        ')
    BRING_BABY_BACK_FROM_DAYCARE_NOTIFICATION_SINGLE_BABY = TunableUiDialogNotificationSnippet(description='\n        The message appearing when a single baby is brought back from daycare.\n        You can reference this single baby by name.\n        ')
    BRING_BABY_BACK_FROM_DAYCARE_NOTIFICATION_MULTIPLE_BABIES = TunableUiDialogNotificationSnippet(description='\n        The message appearing when multiple babies are brought back from\n        daycare. You can not reference any of these babies by name.\n        ')
    SEND_CHILD_TO_NANNY_NOTIFICATION_SINGLE = TunableUiDialogNotificationSnippet(description='\n        The message appears when a single child is sent to the nanny. You can\n        reference this single nanny by name.\n        ')
    SEND_CHILD_TO_NANNY_NOTIFICATION_MULTIPLE = TunableUiDialogNotificationSnippet(description='\n        The message appearing when multiple children are sent to the nanny. You\n        can not reference any of these children by name.\n        ')
    BRING_CHILD_BACK_FROM_NANNY_NOTIFICATION_SINGLE = TunableUiDialogNotificationSnippet(description='\n        The message appearing when a single child is brought back from the\n        nanny. You can reference this single child by name.\n        ')
    BRING_CHILD_BACK_FROM_NANNY_NOTIFICATION_MULTIPLE = TunableUiDialogNotificationSnippet(description='\n        The message appearing when multiple children are brought back from\n        the nanny. You can not reference any of these by name.\n        ')
    GO_TO_DAYCARE_INTERACTION = TunableReference(description='\n        An interaction to push on instantiated Sims that need to go to Daycare.\n        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))
    DAYCARE_AWAY_ACTIONS = TunableMapping(description='\n        Map of commodities to away action.  When the default away action is\n        asked for we look at the ad data of each commodity and select the away\n        action linked to the commodity that is advertising the highest.\n        \n        This set of away actions is used exclusively for Sims in daycare.\n        ', key_type=TunableReference(description='\n            The commodity that we will look at the advertising value for.\n            ', manager=services.get_instance_manager(Types.STATISTIC), class_restrictions=('Commodity',)), value_type=TunableReference(description='\n            The away action that will applied if the key is the highest\n            advertising commodity of the ones listed.\n            ', manager=services.get_instance_manager(Types.AWAY_ACTION)))
Exemplo n.º 12
0
 def display(text,
             title,
             ok_text=3648501874,
             cancel_text=3497542682,
             callback=None):
     text = TurboL18NUtil.get_localized_string(text)
     title = TurboL18NUtil.get_localized_string(title)
     ok_text = TurboL18NUtil.get_localized_string(ok_text)
     cancel_text = TurboL18NUtil.get_localized_string(cancel_text)
     ok_dialog = UiDialogOkCancel.TunableFactory().default(
         TurboUIUtil._get_client_sim(),
         text=lambda *args, **kwargs: text,
         title=lambda *args, **kwargs: title,
         text_ok=lambda *args, **kwargs: ok_text,
         text_cancel=lambda *args, **kwargs: cancel_text)
     if callback is not None:
         ok_dialog.add_listener(callback)
     ok_dialog.show_dialog()
Exemplo n.º 13
0
class MoveInSuperInteraction(ImmediateSuperInteraction):
    INSTANCE_TUNABLES = {
        'dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The dialog box presented to ask if the player should move their Sims in together.'
        ),
        'situation_blacklist':
        event_testing.test_variants.TunableSituationRunningTest()
    }

    def _run_interaction_gen(self, timeline):
        services.sim_info_manager().set_default_genealogy()
        resolver = DataResolver(self.sim.sim_info)
        if not resolver(self.situation_blacklist):
            return True
            yield
        if not self.target.is_sim:
            return True
            yield
        if self.sim.household_id == self.target.household_id:
            return True
            yield

        def on_response(dialog):
            if not dialog.accepted:
                self.cancel_user(
                    cancel_reason_msg=
                    'Move-In. Player canceled, or move in together dialog timed out from client.'
                )
                return
            actor = self.get_participant(ParticipantType.Actor)
            src_household_id = actor.sim_info.household.id
            target = self.target
            tgt_household_id = target.sim_info.household.id
            if src_household_id is not None and tgt_household_id is not None:
                household_split(src_household_id, tgt_household_id)

        dialog = self.dialog(self.sim, self.get_resolver())
        dialog.show_dialog(on_response=on_response)
        return True
        yield
class GenericChooseBetweenTwoAffordancesSuperInteraction(ImmediateSuperInteraction):
    INSTANCE_TUNABLES = {'choice_dialog': UiDialogOkCancel.TunableFactory(description='\n            A Dialog that prompts the user with a two button dialog. The\n            chosen button will result in one of two affordances being chosen.\n            '), 'accept_affordance': SuperInteraction.TunablePackSafeReference(description='\n            The affordance to push on the sim if the user clicks on the \n            accept/ok button.\n            '), 'reject_affordance': SuperInteraction.TunablePackSafeReference(description='\n            The affordance to push on the Sim if the user chooses to click\n            on the reject/cancel button.\n            ')}

    @classmethod
    def _test(cls, target, context, **interaction_parameters):
        if cls.accept_affordance is None and cls.reject_affordance is None:
            return TestResult(False, 'The accept and reject affordances are unavailable with the currently installed packs.')
        return super()._test(target, context, **interaction_parameters)

    def _run_interaction_gen(self, timeline):
        context = self.context.clone_for_sim(self.sim, insert_strategy=interactions.context.QueueInsertStrategy.LAST)
        if self.accept_affordance is None or self.reject_affordance is None:
            affordance = self.accept_affordance or self.reject_affordance
            self.sim.push_super_affordance(affordance, target=self.target, context=context)
            yield

        def _on_response(dialog):
            affordance = self.accept_affordance if dialog.accepted else self.reject_affordance
            self.sim.push_super_affordance(affordance, target=self.target, context=context)

        dialog = self.choice_dialog(self.sim, resolver=SingleSimResolver(self.sim))
        dialog.show_dialog(on_response=_on_response)
Exemplo n.º 15
0
 def display(sim_identifier,
             target_sim_identifier,
             text,
             ok_text,
             cancel_text,
             callback=None):
     sim_info = TurboManagerUtil.Sim.get_sim_info(sim_identifier)
     target_sim_info = TurboManagerUtil.Sim.get_sim_info(
         target_sim_identifier)
     text = TurboL18NUtil.get_localized_string(text)
     ok_text = TurboL18NUtil.get_localized_string(ok_text)
     cancel_text = TurboL18NUtil.get_localized_string(cancel_text)
     resolver = DoubleSimResolver(sim_info, target_sim_info)
     drama_dialog = UiDialogOkCancel.TunableFactory().default(
         sim_info,
         text=lambda *args, **kwargs: text,
         text_cancel=lambda *args, **kwargs: cancel_text,
         text_ok=lambda *args, **kwargs: ok_text,
         target_sim_id=target_sim_info.id,
         resolver=resolver)
     if callback is not None:
         drama_dialog.add_listener(callback)
     drama_dialog.show_dialog()
Exemplo n.º 16
0
class ClubInviteBaseDramaNode(BaseDramaNode):
    INSTANCE_TUNABLES = {
        'sender_sim_info':
        _DramaParticipant(
            description=
            '\n            Specify who the sending Sim must be. This is the Sim that will "text"\n            the Drama Node owner.\n            ',
            excluded_options=('no_participant', ),
            tuning_group=GroupNames.PARTICIPANT),
        'dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The dialog that is displayed to the player Sim once this Drama Node\n            executes. Upon acceptance, the behavior specific to this Drama Node\n            executes.\n            '
        )
    }

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

    def _setup(self, *args, gsi_data=None, **kwargs):
        result = super()._setup(*args, gsi_data=gsi_data, **kwargs)
        if not result:
            return result
        club_service = services.get_club_service()
        if club_service is None:
            if gsi_data is not None:
                gsi_data.rejected_nodes.append(
                    GSIRejectedDramaNodeScoringData(type(self),
                                                    'Club service is None.'))
            return False
        clubs = self._get_possible_clubs()
        if not clubs:
            if gsi_data is not None:
                gsi_data.rejected_nodes.append(
                    GSIRejectedDramaNodeScoringData(
                        type(self), 'No possible clubs found.'))
            return False
        club = random.choice(clubs)
        self._club_id = club.club_id
        return True

    def _get_possible_clubs(self):
        raise NotImplementedError

    def _test(self, resolver, skip_run_tests=False):
        if self._club_id is None:
            return TestResult(False,
                              'Cannot run because there is no chosen node.')
        if self._sender_sim_info is None:
            return TestResult(
                False, 'Cannot run because there is no sender sim info.')
        if not skip_run_tests:
            club = services.get_club_service().get_club_by_id(self._club_id)
            if club is None:
                return TestResult(
                    False, 'Cannot run because the club no longer exists.')
            result = self._test_club(club)
            if not result:
                return result
        return super()._test(resolver, skip_run_tests=skip_run_tests)

    def _test_club(self, club):
        raise NotImplementedError

    def _run(self):
        def on_response(dialog):
            if dialog.accepted:
                club = services.get_club_service().get_club_by_id(
                    self._club_id)
                self._run_club_behavior(club)
            services.drama_scheduler_service().complete_node(self.uid)

        dialog = self.dialog(self._receiver_sim_info,
                             target_sim_id=self._sender_sim_info.id,
                             resolver=self._get_resolver())
        club = services.get_club_service().get_club_by_id(self._club_id)
        dialog.show_dialog(on_response=on_response,
                           additional_tokens=(club.name, ))
        return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE

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

    @classmethod
    def _verify_tuning_callback(cls):
        advertising_data_types = frozenset(
            cls.advertising_configuration.advertising_data_map.keys())
        advertising_types_with_mapped_names = frozenset(
            cls.advertising_name_map.keys())
        advertising_sort_ordered_types = frozenset(
            cls.advertising_name_map.keys())
        if advertising_data_types:
            if advertising_data_types != advertising_types_with_mapped_names:
                logger.error(
                    'Advertising type list {} does not match list of mapped names: {}',
                    advertising_data_types,
                    advertising_types_with_mapped_names)
            if advertising_data_types != advertising_sort_ordered_types:
                logger.error(
                    'Advertising type list {} does not sorted UI list types: {}',
                    advertising_data_types, advertising_sort_ordered_types)
        if cls.advertising_configuration.default_advertising_type is not None and cls.advertising_configuration.default_advertising_type not in advertising_types_with_mapped_names:
            logger.error(
                'Default advertising type {} is not in advertising name map',
                cls.default_advertising_type)
Exemplo n.º 18
0
class UiDialogElement(HasTunableFactory, elements.ParentElement):
    __qualname__ = 'UiDialogElement'
    FACTORY_TUNABLES = {'description': '\n            Prompts the user with an Ok Cancel Dialog. This will cancel an\n            interaction if the user chooses the cancel option.\n            ', 'dialog': UiDialogOkCancel.TunableFactory(description='\n            The dialog to prompt the user with.\n            ')}

    def __init__(self, *args, dialog=None, on_response=None, additional_tokens=(), **kwargs):
        super().__init__(**kwargs)
        self._dialog = dialog(*args, **kwargs)
        self._dialog.add_listener(self._on_response)
        self._result = None
        self._on_response = on_response
        self._additional_tokens = additional_tokens

    def _on_response(self, dialog):
        if self._dialog is None:
            return
        if self._on_response is not None:
            self._result = self._on_response(dialog)
        else:
            self._result = dialog.accepted
        self.trigger_soft_stop()

    def _run(self, timeline):
        self._dialog.show_dialog(additional_tokens=self._additional_tokens)
        if self._result is None:
            return timeline.run_child(element_utils.soft_sleep_forever())
        return self._result

    def _resume(self, timeline, child_result):
        if self._result is not None:
            return self._result
        return False

    def _hard_stop(self):
        super()._hard_stop()
        if self._dialog is not None:
            services.ui_dialog_service().dialog_cancel(self._dialog.dialog_id)
        self._dialog = None

    def _soft_stop(self):
        super()._soft_stop()
        self._dialog = None
Exemplo n.º 19
0
class Situation(BaseSituation, HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SITUATION)):

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

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

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

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

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

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

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

    @classmethod
    def fake_perform_job(cls):
        pass

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def get_target_object(self):
        pass

    def get_created_object(self):
        pass

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

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

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

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

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

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

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

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

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

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

    def _get_goal_tracker(self):
        return self._goal_tracker

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

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

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

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

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

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

    def pre_destroy(self):
        pass

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

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

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

    @classmethod
    def get_predefined_guest_list(cls):
        pass

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

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

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

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

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

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

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

    def is_in_joinable_state(self):
        return True

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

    @classproperty
    def use_spawner_tags_on_travel(cls):
        return cls._use_spawner_tags_on_travel
Exemplo n.º 20
0
class PlayerPlannedDramaNode(BaseDramaNode):
    INSTANCE_TUNABLES = {
        'advance_notice_time':
        TunableTimeSpan(
            description=
            '\n            The number of time between the alert and the start of the event.\n            ',
            default_hours=1,
            locked_args={
                'days': 0,
                'minutes': 0
            }),
        'dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The ok cancel dialog that will display to the user.\n            '
        )
    }

    @classproperty
    def persist_when_active(cls):
        return True

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

    def __init__(self, *args, uid=None, situation_seed=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._situation_seed = situation_seed

    def _run(self):
        situation_seed = self._situation_seed
        if situation_seed is None:
            return DramaNodeRunOutcome.FAILURE
        situation_manager = services.get_zone_situation_manager()
        dialog = self.dialog(self._receiver_sim_info,
                             resolver=self._get_resolver())

        def response(dialog):
            cleanup_node = True
            if dialog.response is not None and dialog.response == ButtonType.DIALOG_RESPONSE_OK:
                cleanup_node = False
                if situation_seed.zone_id == services.current_zone_id():
                    situation_manager.create_situation_from_seed(
                        situation_seed)
                    situation_manager.register_for_callback(
                        situation_seed.situation_id,
                        SituationCallbackOption.END_OF_SITUATION,
                        self._on_planned_drama_node_ended)
                else:
                    situation_manager.travel_seed(situation_seed)
            if cleanup_node:
                services.drama_scheduler_service().complete_node(self.uid)

        dialog.show_dialog(
            on_response=response,
            additional_tokens=(situation_seed.situation_type.display_name, ))
        return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE

    def _on_planned_drama_node_ended(self, situation_id, callback_option, _):
        services.drama_scheduler_service().complete_node(self.uid)

    def on_situation_creation_during_zone_spin_up(self):
        services.get_zone_situation_manager().register_for_callback(
            self._situation_seed.situation_id,
            SituationCallbackOption.END_OF_SITUATION,
            self._on_planned_drama_node_ended)

    def schedule(self,
                 resolver,
                 specific_time=None,
                 time_modifier=TimeSpan.ZERO):
        success = super().schedule(resolver,
                                   specific_time=specific_time,
                                   time_modifier=time_modifier)
        if success:
            services.calendar_service().mark_on_calendar(
                self, advance_notice_time=self.advance_notice_time())
        return success

    def cleanup(self, from_service_stop=False):
        services.calendar_service().remove_on_calendar(self.uid)
        super().cleanup(from_service_stop=from_service_stop)

    def get_calendar_sims(self):
        return tuple(self._situation_seed.invited_sim_infos_gen())

    def create_calendar_entry(self):
        calendar_entry = super().create_calendar_entry()
        situation_type = self._situation_seed.situation_type
        calendar_entry.zone_id = self._situation_seed.zone_id
        build_icon_info_msg(
            IconInfoData(icon_resource=situation_type.calendar_icon),
            situation_type.display_name, calendar_entry.icon_info)
        calendar_entry.scoring_enabled = self._situation_seed.scoring_enabled
        return calendar_entry

    def create_calendar_alert(self):
        calendar_alert = super().create_calendar_alert()
        situation_type = self._situation_seed.situation_type
        calendar_alert.zone_id = self._situation_seed.zone_id
        if self._situation_seed.situation_type.calendar_alert_description is not None:
            calendar_alert.description = situation_type.calendar_alert_description
        build_icon_info_msg(
            IconInfoData(icon_resource=situation_type.calendar_icon),
            situation_type.display_name, calendar_alert.calendar_icon)
        return calendar_alert

    def save(self, drama_node_proto):
        super().save(drama_node_proto)
        self._situation_seed.serialize_to_proto(
            drama_node_proto.stored_situation)

    def load(self, drama_node_proto, schedule_alarm=True):
        super_success = super().load(drama_node_proto,
                                     schedule_alarm=schedule_alarm)
        if not super_success:
            return False
        self._situation_seed = SituationSeed.deserialize_from_proto(
            drama_node_proto.stored_situation)
        if not self.get_sender_sim_info().is_npc:
            services.calendar_service().mark_on_calendar(
                self, advance_notice_time=self.advance_notice_time())
        return True
Exemplo n.º 21
0
class CheatCommandTuning:
    __qualname__ = 'CheatCommandTuning'
    ENABLE_CHEATS_DIALOG = UiDialogOkCancel.TunableFactory()
    JOKES = TunableList(str)
Exemplo n.º 22
0
class SituationDramaNode(BaseDramaNode):
    INSTANCE_TUNABLES = {
        'situation_to_run':
        TunableReference(
            description=
            '\n            The situation that this drama node will try and start.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION),
            tuning_group=GroupNames.SITUATION),
        'sender_sim_info_job':
        OptionalTunable(
            description=
            '\n            When enabled, this job will be assigned to sender sim.\n            A validation error will be thrown if sender_sim_info_job is set\n            but not sender_sim_info.\n            ',
            tunable=SituationJob.TunableReference(
                description=
                '\n                The default job for the sender of this drama node.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'host_sim_info_job':
        OptionalTunable(
            description=
            '\n            If enabled, this job will be assigned to the host sim.\n            ',
            tunable=SituationJob.TunableReference(
                description=
                '\n                Situation Job for the host sim of the situation to run.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'notification':
        OptionalTunable(
            description=
            '\n            If enabled this is the notification that will be displayed after\n            the situation is started.\n            ',
            tunable=UiDialogNotification.TunableFactory(
                description=
                '\n                The notification that displays when the situation is started.\n                '
            )),
        'household_milestone':
        OptionalTunable(
            description=
            '\n            If enabled, the household milestone will reset when the situation runs.\n            Only resets if the situation is run successfully. Useful for situations\n            that are spun up when a household milestone is unlocked.\n            ',
            tunable=TunableReference(
                pack_safe=True,
                manager=services.get_instance_manager(
                    sims4.resources.Types.HOUSEHOLD_MILESTONE))),
        'homelot_only':
        Tunable(
            description=
            '\n            If checked, the situation will only be permitted to run if the active\n            sims are on their homelot.\n            ',
            tunable_type=bool,
            default=False),
        'host_sim_participant':
        OptionalTunable(
            description=
            '\n            If enabled, the participant type to use to find who the host Sim\n            of the situation will be.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The participant type to use to find the host Sim.\n                ',
                tunable_type=ParticipantTypeSingleSim,
                default=ParticipantTypeSingleSim.Actor),
            tuning_group=GroupNames.PARTICIPANT),
        'lot':
        OptionalTunable(
            description=
            '\n            If enabled, the Sim will travel to this zone for the situation.\n            ',
            tunable=TunablePackSafeLotDescription(
                description=
                '\n                A reference to the lot description file for this situation. \n                This is used for easier zone ID lookups.\n                '
            )),
        'confirm_dialog':
        OptionalTunable(
            description=
            '\n            If enabled, a dialog will appear when this drama node runs, asking\n            the user if they wish to proceed.\n            ',
            tunable=UiDialogOkCancel.TunableFactory(
                description=
                '\n                The dialog with ok/cancel buttons that will display, asking \n                the user if they want to proceed with the situation on this\n                drama node.\n                '
            )),
        'advance_notice_time':
        TunableTimeSpan(
            description=
            '\n            The amount of time between the alert and the start of the event.\n            ',
            default_minutes=0,
            default_hours=0,
            default_days=0),
        'user_facing':
        Tunable(
            description=
            '\n            If this situation should be user facing or not.\n            ',
            tunable_type=bool,
            default=False)
    }

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

    @classproperty
    def spawn_sims_during_zone_spin_up(cls):
        return False

    def create_calendar_alert(self):
        calendar_alert = super().create_calendar_alert()
        if self.ui_display_data:
            build_icon_info_msg(
                IconInfoData(icon_resource=self.ui_display_data.icon),
                self.ui_display_data.name, calendar_alert.calendar_icon)
        return calendar_alert

    def load(self, drama_node_proto, schedule_alarm=True):
        success = super().load(drama_node_proto, schedule_alarm=schedule_alarm)
        if success and self.ui_display_type != DramaNodeUiDisplayType.NO_UI:
            services.calendar_service().mark_on_calendar(
                self, self.advance_notice_time())
        return success

    def schedule(self,
                 resolver,
                 specific_time=None,
                 time_modifier=TimeSpan.ZERO,
                 **kwargs):
        success = super().schedule(resolver,
                                   specific_time=specific_time,
                                   time_modifier=time_modifier,
                                   **kwargs)
        if success and self.ui_display_type != DramaNodeUiDisplayType.NO_UI:
            services.calendar_service().mark_on_calendar(
                self, self.advance_notice_time())
        return success

    def get_calendar_sims(self):
        return (self._receiver_sim_info, )

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.sender_sim_info_job is not None and cls.sender_sim_info.type == DramaNodeParticipantOption.DRAMA_PARTICIPANT_OPTION_NONE:
            logger.error(
                'Setting sender sim info job but sender sim info is set to None for {}. Please make sure that sender sim info is set correctly.',
                cls)
        if cls.host_sim_info_job is not None and cls.host_sim_participant is None:
            logger.error(
                'Setting host_sim_info_job but host_sim_participant is None in {}. Please set host_sim_participant to a valid participant.',
                cls,
                owner='nsavalani')

    def _test(self, resolver, skip_run_tests=False):
        homelot_id = services.active_household().home_zone_id
        if self.homelot_only and homelot_id != services.current_zone_id():
            return TestResult(
                False,
                'Cannot run because the current zone is not the home lot.')
        if self.host_sim_participant is not None:
            host_sim_info = self._get_resolver().get_participant(
                self.host_sim_participant)
            if host_sim_info is None:
                return TestResult(
                    False, 'Cannot run because there is no host sim info.')
        return super()._test(resolver, skip_run_tests=skip_run_tests)

    def _run(self):
        if self.confirm_dialog:
            sim_info = self._receiver_sim_info
            resolver = DoubleSimResolver(self._sender_sim_info,
                                         self._receiver_sim_info)
            confirm_dialog = self.confirm_dialog(sim_info, resolver=resolver)
            confirm_dialog.show_dialog(
                on_response=self._on_confirm_dialog_response)
            return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE
        else:
            self._run_situation()
            return DramaNodeRunOutcome.SUCCESS_NODE_COMPLETE

    def _on_confirm_dialog_response(self, dialog):
        if dialog.accepted:
            self._run_situation()
        services.drama_scheduler_service().complete_node(self.uid)

    def _run_situation(self):
        host_sim_id = 0
        host_sim_info = None
        if self.host_sim_participant is not None:
            host_sim_info = self._get_resolver().get_participant(
                self.host_sim_participant)
            host_sim_id = host_sim_info.id
        guest_list = self.situation_to_run.get_predefined_guest_list()
        if guest_list is None:
            guest_list = SituationGuestList(invite_only=True,
                                            host_sim_id=host_sim_id)
        if self._sender_sim_info is not None and self.sender_sim_info_job is not None:
            guest_list.add_guest_info(
                SituationGuestInfo.construct_from_purpose(
                    self._sender_sim_info.id, self.sender_sim_info_job,
                    SituationInvitationPurpose.INVITED))
        if host_sim_info is not None and self.host_sim_info_job is not None:
            guest_list.add_guest_info(
                SituationGuestInfo.construct_from_purpose(
                    host_sim_info.id, self.host_sim_info_job,
                    SituationInvitationPurpose.HOSTING))
        zone_id = 0
        if self.lot is not None:
            lot_id = get_lot_id_from_instance_id(self.lot)
            zone_id = services.get_persistence_service(
            ).resolve_lot_id_into_zone_id(lot_id, ignore_neighborhood_id=True)
        situation_id = services.get_zone_situation_manager().create_situation(
            self.situation_to_run,
            guest_list=guest_list,
            spawn_sims_during_zone_spin_up=self.spawn_sims_during_zone_spin_up,
            user_facing=self.user_facing,
            zone_id=zone_id)
        if self.household_milestone and situation_id:
            active_household_milestone_tracker = services.active_household(
            ).household_milestone_tracker
            active_household_milestone_tracker.reset_milestone(
                self.household_milestone)
        if self.notification is not None:
            target_sim_id = self._sender_sim_info.id if self._sender_sim_info is not None else None
            dialog = self.notification(self._receiver_sim_info,
                                       DoubleSimResolver(
                                           self._sender_sim_info,
                                           self._receiver_sim_info),
                                       target_sim_id=target_sim_id)
            dialog.show_dialog()
        return True
Exemplo n.º 23
0
class _WaitingForServiceState(_VetCustomerGroupSituationStateBase):
    FACTORY_TUNABLES = {
        'service_request_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            When this interaction is run by the player on the customer, we will pop up\n            the service request dialog.\n            ',
            tuning_group=GroupNames.SITUATION),
        'service_request_dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The dialog to display when service_request_interaction runs.\n            \n            The tokens passed in will be the Pet Sim, and the Pet Owner Sim,\n            in that order.\n            ',
            tuning_group=GroupNames.SITUATION),
        'take_on_customer_interaction':
        TunableReference(
            description=
            '\n            When the service request dialog is accepted, the vet will \n            run the specified interaction on the pet.',
            manager=services.get_instance_manager(Types.INTERACTION),
            class_restrictions=('SuperInteraction', ),
            tuning_group=GroupNames.SITUATION)
    }

    def __init__(self, service_request_interaction, service_request_dialog,
                 take_on_customer_interaction, **kwargs):
        super().__init__(**kwargs)
        self._showing_dialog = False
        self.service_request_interaction = service_request_interaction
        self.service_request_dialog = service_request_dialog
        self.take_on_customer_interaction = take_on_customer_interaction

    def on_activate(self, reader=None):
        super().on_activate(reader)
        for custom_key in self._interaction_of_interest.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionStart, custom_key)
        for custom_key in self.service_request_interaction.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionComplete,
                                      custom_key)
        self.owner.began_waiting()

    def handle_event(self, sim_info, event, resolver):
        if not self._additional_tests(sim_info, event, resolver):
            return
        if event == TestEvent.InteractionStart and resolver(
                self._interaction_of_interest):
            assigned_vet = resolver.get_participant(ParticipantType.Actor)
            if not assigned_vet.is_selectable:
                self.owner.assign_to_vet(assigned_vet)
        if event == TestEvent.InteractionComplete:
            actor_sim_info = resolver.get_participant(ParticipantType.Actor)
            if actor_sim_info.is_selectable and resolver(
                    self.service_request_interaction):
                self._on_player_interaction_complete(actor_sim_info)
            elif resolver(self._interaction_of_interest):
                self._on_interaction_complete_by_actor(actor_sim_info)

    def _on_player_interaction_complete(self, actor_sim_info):
        if self._showing_dialog:
            return
        self._showing_dialog = True
        target_sim = self.owner.get_pet_owner()
        pet_sim = self.owner.get_pet()
        if target_sim is None or pet_sim is None:
            self.owner._self_destruct()
        self.owner.log_flow_entry('Presenting Service Request Dialog')
        dialog = self.service_request_dialog(actor_sim_info,
                                             resolver=DoubleSimResolver(
                                                 pet_sim, target_sim))
        dialog.show_dialog(on_response=self._on_dialog_response)

    def _on_interaction_complete_by_actor(self, actor_sim_info):
        if not self._showing_dialog:
            if self.owner.assigned_vet is not actor_sim_info:
                self.owner.assign_to_vet(actor_sim_info)
            self._go_to_next_state()

    def _additional_tests(self, sim_info, event, resolver):
        target_sim = resolver.get_participant(ParticipantType.TargetSim)
        if not self.owner.is_sim_info_in_situation(target_sim):
            return False
        return True

    def _on_dialog_response(self, dialog):
        self._showing_dialog = False
        if dialog.response == dialog.response == ButtonType.DIALOG_RESPONSE_OK:
            assigned_vet = dialog.owner.get_sim_instance()
            pet = self.owner.get_pet()
            if assigned_vet is None or pet is None:
                self.owner._self_destruct()
                return
            context = InteractionContext(
                assigned_vet,
                InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT,
                Priority.High,
                insert_strategy=QueueInsertStrategy.NEXT)
            assigned_vet.push_super_affordance(
                self.take_on_customer_interaction, pet, context)
            self.owner.assign_to_vet(assigned_vet)
            self._go_to_next_state()

    def _next_state(self):
        return self.owner._service_state()
Exemplo n.º 24
0
class HaveBabyAtHospitalInteraction(DeliverBabySuperInteraction):
    INSTANCE_TUNABLES = {
        'partner_affordance':
        TunableReference(
            description=
            '\n             When the Pregnant Sim leaves the lot to give birth, this is the affordance \n             that will get pushed on the other Sim involved with the pregnancy if\n             there is one and the Sim is on lot.\n             ',
            manager=services.affordance_manager()),
        'off_lot_birth_dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            This dialog informs the player that the babies are on the home lot\n            and they can follow the birthing Sim to their home lot. We always\n            display this, even if the birthing Sim is not the last selectable\n            one on the lot.\n            '
        )
    }

    def _pre_perform(self, *args, **kwargs):
        self.add_liability(interactions.rabbit_hole.RABBIT_HOLE_LIABILTIY,
                           interactions.rabbit_hole.RabbitHoleLiability(self))
        return super()._pre_perform(*args, **kwargs)

    def _complete_pregnancy_gen(self, timeline, pregnancy_tracker):
        is_off_lot_birth = False
        baby_sim_infos = []
        for offspring_data in pregnancy_tracker.get_offspring_data_gen():
            sim_info = pregnancy_tracker.create_sim_info(offspring_data)
            current_zone = services.current_zone()
            if current_zone.id == sim_info.zone_id:
                services.daycare_service().exclude_sim_from_daycare(sim_info)
                if not assign_bassinet_for_baby(sim_info):
                    create_and_place_baby(sim_info)
            else:
                is_off_lot_birth = True
            baby_sim_infos.append(sim_info)
        offspring_count = pregnancy_tracker.offspring_count
        pregnancy_tracker.complete_pregnancy()
        self._apply_inherited_loots(baby_sim_infos, pregnancy_tracker)
        if is_off_lot_birth:
            travel_liability = TravelSimLiability(
                self,
                self.sim.sim_info,
                self.sim.sim_info.household.home_zone_id,
                expecting_dialog_response=True)
            self.add_liability(TRAVEL_SIM_LIABILITY, travel_liability)

            def on_travel_dialog_response(dialog):
                if dialog.accepted:
                    if self.outcome is not None:
                        loot = LootOperationList(self.get_resolver(),
                                                 self.outcome.get_loot_list())
                        loot.apply_operations()
                    save_lock_liability = self.get_liability(
                        SaveLockLiability.LIABILITY_TOKEN)
                    if save_lock_liability is not None:
                        save_lock_liability.release()
                    travel_liability.travel_dialog_response(dialog)

            travel_dialog_element = UiDialogElement(
                self.sim,
                self.get_resolver(),
                dialog=self.off_lot_birth_dialog,
                on_response=on_travel_dialog_response,
                additional_tokens=(offspring_count, ))
            result = yield from element_utils.run_child(
                timeline, travel_dialog_element)
            return result
            yield
        return True
        yield

    def prepare_gen(self, timeline, *args, **kwargs):
        result = yield from super().prepare_gen(timeline, *args, **kwargs)
        if result != InteractionQueuePreparationStatus.FAILURE:
            self._push_spouse_to_hospital()
        return result
        yield

    def _push_spouse_to_hospital(self):
        pregnancy_tracker = self.sim.sim_info.pregnancy_tracker
        sim_info = None
        (parent_a, parent_b) = pregnancy_tracker.get_parents()
        if parent_a is not None:
            if parent_b is not None:
                if parent_a.sim_id == self.sim.sim_id:
                    sim_info = parent_b
                else:
                    sim_info = parent_a
        if sim_info is None:
            return
        sim = sim_info.get_sim_instance()
        if sim is None:
            return
        if sim.queue.has_duplicate_super_affordance(self.partner_affordance,
                                                    sim, None):
            return
        context = interactions.context.InteractionContext(
            sim, interactions.context.InteractionContext.SOURCE_SCRIPT,
            interactions.priority.Priority.High)
        result = sim.push_super_affordance(self.partner_affordance, sim,
                                           context)
        if result:
            interaction = result.interaction
            interaction.add_liability(
                interactions.rabbit_hole.RABBIT_HOLE_LIABILTIY,
                interactions.rabbit_hole.RabbitHoleLiability(interaction))
            liability = CancelInteractionsOnExitLiability()
            self.add_liability(CANCEL_INTERACTION_ON_EXIT_LIABILITY, liability)
            liability.add_cancel_entry(parent_b, interaction)
        return result
Exemplo n.º 25
0
class PhoneCall(HasTunableFactory):
    __qualname__ = 'PhoneCall'
    FACTORY_TUNABLES = {'weight': Tunable(description='\n                The weight that this phone call type will be chosen.\n                ', tunable_type=float, default=1.0), 'dialog': UiDialogOkCancel.TunableFactory(description='\n                The message that will be displayed when the phone is picked up.\n                '), 'sim_filter': TunableReference(description='\n                The filter that determines the NPC target of the phone call.\n                ', manager=services.get_instance_manager(sims4.resources.Types.SIM_FILTER)), 'tests': TunableTestSet(description='\n                Tests that will be run on the sim when testing to see if this\n                phone call should be allowed.\n                ')}

    def __init__(self, sim, weight=None, dialog=None, sim_filter=None, tests=None):
        self._sim = sim
        self._weight = weight
        self._dialog = dialog
        self._sim_filter = sim_filter
        self._target = None
        self._tests = tests

    @property
    def weight(self):
        return self._weight

    def try_and_setup(self):
        if not self._test():
            return False
        self._generate_target()
        if self._target is None:
            return False
        return True

    def _on_dialog_accepted(self):
        raise NotImplementedError

    def _dialog_callback(self, dialog):
        services.sim_filter_service().remove_sim_id_from_global_blacklist(self._target.id, SimFilterGlobalBlacklistReason.PHONE_CALL)
        if not dialog.accepted:
            return
        self._on_dialog_accepted()

    def execute(self):
        services.sim_filter_service().add_sim_id_to_global_blacklist(self._target.id, SimFilterGlobalBlacklistReason.PHONE_CALL)
        dialog = self._dialog(self._sim, DoubleSimResolver(self._sim.sim_info, self._target))
        dialog.show_dialog(on_response=self._dialog_callback)

    def _test(self):
        resolver = SingleSimResolver(self._sim.sim_info)
        return self._tests.run_tests(resolver)

    def _generate_target(self):
        blacklist_sim_ids = [sim.id for sim in services.sim_info_manager().instanced_sims_gen()]
        filter_results = services.sim_filter_service().submit_filter(self._sim_filter, None, requesting_sim_info=self._sim.sim_info, blacklist_sim_ids=blacklist_sim_ids, allow_yielding=False)
        if filter_results:
            self._target = sims4.random.weighted_random_item([(result.score, result.sim_info) for result in filter_results])