def ui_dialog_notification_test(*all_text, _connection=None):
    client = services.client_manager().get(_connection)
    all_text_str = ' '.join(all_text)
    if '/' in all_text:
        (title, text) = all_text_str.split('/')
        notification = UiDialogNotification.TunableFactory().default(client.active_sim, text=lambda **_: LocalizationHelperTuning.get_raw_text(text), title=lambda **_: LocalizationHelperTuning.get_raw_text(title))
    else:
        notification = UiDialogNotification.TunableFactory().default(client.active_sim, text=lambda **_: LocalizationHelperTuning.get_raw_text(all_text_str))
    notification.show_dialog(icon_override=(None, client.active_sim))
示例#2
0
def _get_notification_tunable_factory(**kwargs):
    return UiDialogNotification.TunableFactory(locked_args={
        'text_tokens': DEFAULT,
        'icon': None,
        'secondary_icon': None
    },
                                               **kwargs)
示例#3
0
def show_unsuccessful_server_host():
    title = "Nobody connected to the host in time."

    notification = UiDialogNotification.TunableFactory().default(
        services.get_active_sim(),
        title=lambda **_: LocalizationHelperTuning.get_raw_text(title))
    notification.show_dialog(icon_override=(None, services.get_active_sim()))
示例#4
0
def show_server_host_attempt():
    title = "Attempting to host the game."

    notification = UiDialogNotification.TunableFactory().default(
        services.get_active_sim(),
        title=lambda **_: LocalizationHelperTuning.get_raw_text(title))
    notification.show_dialog(icon_override=(None, services.get_active_sim()))
示例#5
0
def show_client_connect_on_server():
    title = "A client has connected successfully!"

    notification = UiDialogNotification.TunableFactory().default(
        services.get_active_sim(),
        title=lambda **_: LocalizationHelperTuning.get_raw_text(title))
    notification.show_dialog(icon_override=(None, services.get_active_sim()))
示例#6
0
class _notification_and_loot(_dialog_and_loot):
    FACTORY_TUNABLES = {
        'notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            The notification that will display to the drama node.\n            '
        ),
        'loot_list':
        TunableList(
            description=
            '\n            A list of loot operations to apply when this notification is given.\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.notification(drama_node._receiver_sim_info,
                                   target_sim_id=target_sim_id,
                                   resolver=resolver)
        dialog.show_dialog()
        for loot_action in self.loot_list:
            loot_action.apply_to_resolver(resolver)
示例#7
0
class RingDoorbellSuperInteraction(SuperInteraction):
    __qualname__ = 'RingDoorbellSuperInteraction'
    INSTANCE_TUNABLES = {'_nobody_home_failure_notification': UiDialogNotification.TunableFactory(description='\n                Notification that displays if no one was home when they tried\n                to ring the doorbell.\n                '), '_bad_relationship_failure_notification': UiDialogNotification.TunableFactory(description="\n                Notification that displays if there wasn't high enough\n                relationship with any of the household members when they\n                tried to ring the doorbell.\n                "), '_success_notification': UiDialogNotification.TunableFactory(description='\n                Notification that displays if the user succeeded in becoming\n                greeted when they rang the doorbell.\n                '), '_relationship_test': TunableRelationshipTest(description='\n                The Relationship test ran between the sim running the\n                interaction and all of the npc family members to see if they\n                are allowed in.\n                ', locked_args={'subject': ParticipantType.Actor, 'target_sim': ParticipantType.TargetSim})}

    def _try_to_be_invited_in(self):
        owner_household = services.household_manager().get(services.current_zone().lot.owner_household_id)
        resolver = self.get_resolver()
        if owner_household is None:
            dialog = self._nobody_home_failure_notification(self.sim, resolver)
            dialog.show_dialog()
            return
        owner_household_sims = tuple(owner_household.instanced_sims_gen())
        if not owner_household_sims:
            dialog = self._nobody_home_failure_notification(self.sim, resolver)
            dialog.show_dialog()
            return
        for target_sim in owner_household_sims:
            relationship_resolver = DoubleSimResolver(self.sim.sim_info, target_sim.sim_info)
            while relationship_resolver(self._relationship_test):
                dialog = self._success_notification(self.sim, resolver)
                dialog.show_dialog()
                services.get_zone_situation_manager().make_waiting_player_greeted(self.sim)
                return
        dialog = self._bad_relationship_failure_notification(self.sim, resolver)
        dialog.show_dialog()

    def _post_perform(self):
        super()._post_perform()
        self.add_exit_function(self._try_to_be_invited_in)
示例#8
0
class BindPetFamiliar(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'participant':
        TunableEnumEntry(
            description=
            '\n            The Participant to Bind as a Familiar to the Sim.\n            ',
            tunable_type=ParticipantTypeSingle,
            default=ParticipantTypeSingle.TargetSim),
        'bound_familiar_tns':
        UiDialogNotification.TunableFactory(
            description=
            '\n            TNS that is displayed when this pet is bound as a familiar.\n            '
        )
    }

    def bind_familiar(self, interaction, familiar_owner, familiar_tracker):
        familiar = interaction.get_participant(self.participant)
        if familiar.species == Species.CAT:
            familiar_type = FamiliarType.CAT
        elif familiar.species == Species.DOG:
            familiar_type = FamiliarType.DOG
        else:
            logger.error(
                'Attempting to bind a Sim, {}, of species, {}, as a familiar which is unsupported behavior.',
                familiar, familiar.species)
            return
        familiar_tracker.bind_familiar(familiar_type, pet_familiar=familiar)
        dialog = self.bound_familiar_tns(familiar_owner,
                                         interaction.get_resolver())
        dialog.show_dialog()
class HouseholdMilestone(Milestone,
                         metaclass=HashedTunedInstanceMetaclass,
                         manager=services.get_instance_manager(
                             sims4.resources.Types.HOUSEHOLD_MILESTONE)):
    INSTANCE_TUNABLES = {
        'notification':
        OptionalTunable(
            description=
            '\n            If enabled then we will display a notification when this milestone\n            is completed.\n            ',
            tunable=UiDialogNotification.TunableFactory(
                description=
                '\n                This text will display in a notification pop up when completed.\n                '
            ))
    }

    @classmethod
    def handle_event(cls, sim_info, event, resolver):
        if sim_info is None:
            return
        if sim_info.household is None:
            logger.error(
                "Household doesn't exist for milestone {} and SimInfo {}",
                cls,
                sim_info,
                owner='camilogarcia')
            return
        household_milestone_tracker = sim_info.household.household_milestone_tracker
        if household_milestone_tracker is None:
            return
        household_milestone_tracker.handle_event(cls, event, resolver)

    @classmethod
    def register_callbacks(cls):
        tests = [objective.objective_test for objective in cls.objectives]
        services.get_event_manager().register_tests(cls, tests)
示例#10
0
class NothingToSeeHereState(CommonInteractionCompletedSituationState):
    FACTORY_TUNABLES = {
        'timeout_notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            The notification that will play if the situation times out.\n            '
        )
    }

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

    def _end_situation(self):
        active_sim_info = services.active_sim_info()
        agent = next(iter(self.owner.all_sims_in_situation_gen()))
        notification = self._timeout_notification(active_sim_info,
                                                  resolver=DoubleSimResolver(
                                                      active_sim_info, agent))
        notification.show_dialog()
        situation_manger = services.get_zone_situation_manager()
        for sim in self.owner.all_sims_in_situation_gen():
            situation_manger.make_sim_leave_now_must_run(sim)
        self.owner._self_destruct()

    def _on_interaction_of_interest_complete(self, **kwargs):
        self._end_situation()

    def timer_expired(self):
        self._end_situation()
示例#11
0
def show_client_connection_failure():
    title = "Connection failed. Perhaps the host and port you specified is incorrect?"

    notification = UiDialogNotification.TunableFactory().default(
        services.get_active_sim(),
        title=lambda **_: LocalizationHelperTuning.get_raw_text(title))
    notification.show_dialog(icon_override=(None, services.get_active_sim()))
class AspirationNotification(AspirationBasic):
    __qualname__ = 'AspirationNotification'
    INSTANCE_TUNABLES = {
        'objectives':
        sims4.tuning.tunable.TunableList(
            description=
            '\n            A Set of objectives for completing an aspiration.',
            tunable=sims4.tuning.tunable.TunableReference(
                description='\n                One objective for an aspiration',
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECTIVE))),
        'disabled':
        sims4.tuning.tunable.Tunable(
            description=
            '\n            Checking this box will remove this Aspiration from the event system and the UI, but preserve the tuning.',
            tunable_type=bool,
            default=False),
        'notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            This text will display in a notification pop up when completed.\n            '
        )
    }

    @classmethod
    def aspiration_type(cls):
        return AspriationType.NOTIFICATION
示例#13
0
 def __init__(self, **kwargs):
     super().__init__(
         collection_id=TunableEnumEntry(
             description=
             '\n                            Unique Id for this collectible, cannot be re-used.\n                            ',
             tunable_type=CollectionIdentifier,
             default=CollectionIdentifier.Unindentified,
             export_modes=ExportModes.All),
         collection_name=TunableLocalizedString(
             description=
             '\n                            Localization String For the name of the \n                            collection.  This will be read on the collection\n                            UI to separate each item group.\n                            ',
             export_modes=ExportModes.All),
         completed_award=TunableReference(
             description=
             '\n                            Object award when the collection is completed.  \n                            This is an object that will be awarded to the Sim\n                            when all the items inside a collection have been \n                            discovered.\n                            ',
             manager=services.definition_manager(),
             export_modes=ExportModes.All),
         completed_award_money=TunableRange(
             description=
             '\n                            Money award when the collection is completed.  \n                            ',
             tunable_type=int,
             default=100,
             minimum=0,
             export_modes=ExportModes.All),
         completed_award_notification=UiDialogNotification.TunableFactory(
             description=
             '\n                            Notification that will be shown when the collection\n                            is completed and the completed_award is given.\n                            '
         ),
         object_list=TunableList(
             description=
             '\n                            List of object that belong to a collectible group.\n                            ',
             tunable=CollectibleTuple.TunableFactory(),
             export_modes=ExportModes.All),
         screen_slam=OptionalTunable(
             description=
             '\n                             Screen slam to show when the collection is\n                             completed and the completed_award is given.\n                             Localization Tokens: Collection Name = {0.String}\n                             ',
             tunable=ui.screen_slam.TunableScreenSlamSnippet()),
         first_collected_notification=OptionalTunable(
             description=
             '\n                            If enabled a notification will be displayed when\n                            the first item of this collection has been found.\n                            ',
             tunable=UiDialogNotification.TunableFactory(
                 description=
                 '\n                                Notification that will be shown the first item of\n                                this collection has been found.\n                                '
             ),
             disabled_name='No_notification',
             enabled_name='Display_notification'))
示例#14
0
def show_notif(sim, text):
    title = "{} said".format(
        Distributor.instance().get_distributor_with_active_sim_matching_sim_id(
            sim.id).client._account.persona_name)
    notification = UiDialogNotification.TunableFactory().default(
        sim,
        text=lambda **_: LocalizationHelperTuning.get_raw_text(text),
        title=lambda **_: LocalizationHelperTuning.get_raw_text(title))
    notification.show_dialog(icon_override=(None, sim))
class MultiPickerInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {
        'picker_dialog':
        UiMultiPicker.TunableFactory(
            description=
            '\n           Tuning for the ui multi picker. \n           ',
            tuning_group=GroupNames.PICKERTUNING),
        'continuation':
        OptionalTunable(
            description=
            '\n            If enabled, you can tune a continuation to be pushed.\n            Do not use PickedObjects or PickedSims as we are not setting those\n            directly.\n            ',
            tunable=TunableContinuation(
                description=
                '\n                If specified, a continuation to push.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING),
        'success_notification':
        OptionalTunable(
            description=
            '\n            When enabled this dialog will be displayed when the multi picker\n            is accepted and has changes new information.\n            ',
            tunable=UiDialogNotification.TunableFactory(
                description=
                '\n                The notification that is displayed when a multi picker interaction\n                is accepted with new information.\n                '
            ),
            tuning_group=GroupNames.PICKERTUNING)
    }

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

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

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

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

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

    def _handle_successful_editing(self):
        self._push_continuation()
        if self.success_notification is not None:
            resolver = self.get_resolver()
            dialog = self.success_notification(self.sim, resolver)
            dialog.show_dialog()
 def _create_dialog(self) -> Union[UiDialogNotification, None]:
     return UiDialogNotification.TunableFactory().default(
         None,
         title=lambda *args, **kwargs: self.title,
         text=lambda *args, **kwargs: self.description,
         visual_type=self.visual_type,
         urgency=self.urgency,
         information_level=self.information_level,
         ui_responses=self.ui_responses,
         expand_behavior=self.expand_behavior)
示例#17
0
def show_notification(text, title=None):
    client = services.client_manager().get_first_client()
    if title is None:
        title = 'Neutron Core'

    localized_title = lambda **_: LocalizationHelperTuning.get_raw_text(title)
    localized_text = lambda **_: LocalizationHelperTuning.get_raw_text(text)

    notification = UiDialogNotification.TunableFactory().default(
        client.active_sim, text=localized_text, title=localized_title)
    notification.show_dialog()
    def _create_dialog(self) -> Union[UiDialogNotification, None]:
        """_create_dialog()
        Create a dialog for use in :func:``show`.

        .. note:: Override this method with any arguments you want to.
        """
        return UiDialogNotification.TunableFactory().default(
            None,
            title=lambda *args, **kwargs: self.title,
            text=lambda *args, **kwargs: self.description,
            visual_type=self.visual_type,
            urgency=self.urgency,
            information_level=self.information_level,
            ui_responses=self.ui_responses,
            expand_behavior=self.expand_behavior)
示例#19
0
class ShowHighChanceScholarshipsLoot(BaseLootOperation):
    SCHOLARSHIP_AMOUNT_DISPLAYED = TunableRange(
        description=
        "\n        The number of scholarships to display to the Sim in a notification\n        after running 'Get_Guidance_Counselor_Advice' loot action.\n        ",
        tunable_type=int,
        default=3,
        minimum=1)
    HIGH_CHANCE_SCHOLARSHIP_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        Message when a Sim requests to show scholarships they have the best chance\n        to win.\n        '
    )

    def _apply_to_subject_and_target(self, subject, target, resolver):
        dialog = self.HIGH_CHANCE_SCHOLARSHIP_NOTIFICATION(subject, None)
        degree_tracker = subject.degree_tracker
        if degree_tracker is None:
            return
        high_chance_scholarships = degree_tracker.get_best_scored_scholarship_list(
            self.SCHOLARSHIP_AMOUNT_DISPLAYED, resolver, subject)
        dialog.show_dialog(
            secondary_icon_override=IconInfoData(obj_instance=subject),
            additional_tokens=(subject, high_chance_scholarships))
示例#20
0
 def display(text,
             title,
             visual_type=None,
             urgency=None,
             information_level=None,
             icons=None):
     text = TurboL18NUtil.get_localized_string(text)
     title = TurboL18NUtil.get_localized_string(title)
     if visual_type is None:
         visual_type = TurboUIUtil.Notification.UiDialogNotificationVisualType.INFORMATION
     if urgency is None:
         urgency = TurboUIUtil.Notification.UiDialogNotificationUrgency.DEFAULT
     if information_level is None:
         information_level = TurboUIUtil.Notification.UiDialogNotificationLevel.SIM
     notification = UiDialogNotification.TunableFactory().default(
         None,
         text=lambda *args, **kwargs: text,
         title=lambda *args, **kwargs: title,
         visual_type=visual_type,
         urgency=urgency,
         information_level=information_level)
     notification.show_dialog(icon_override=icons)
class VenueEventDramaNode(VenueEventDramaNodeDisplayMixin, BaseDramaNode):
    GO_TO_VENUE_ZONE_INTERACTION = TunablePackSafeReference(
        description=
        '\n        Reference to the interaction used to travel the Sims to the zone of the venue.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
    INSTANCE_TUNABLES = {
        'duration':
        TunableSimMinute(
            description=
            '\n            The duration that this drama node will run for.\n            ',
            minimum=1,
            default=1),
        'zone_director':
        OptionalTunable(
            description=
            '\n            If enabled then this drama node will override the zone director\n            of the lot.\n            ',
            tunable=TunableReference(
                description=
                '\n                The zone director that we will override onto the lot.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ZONE_DIRECTOR))),
        'notification':
        OptionalTunable(
            description=
            '\n            If enabled then we will display a notification when this venue\n            event occurs.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'away_notification':
        OptionalTunable(
            description=
            '\n            If enabled then we will display a notification when this venue\n            event occurs if player is not on the lot.\n            Additional Tokens:\n            Zone Name\n            Venue Name\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'ending_notification':
        OptionalTunable(
            description=
            '\n            If enabled then we will display a notification when this venue\n            event ends if the player is on the current lot that the event is\n            taking place on.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'zone_modifier_requirements':
        TunableWhiteBlackList(
            description=
            '\n            A requirement on zone modifiers which must be true on both\n            scheduling and running.\n            ',
            tunable=TunableReference(
                description=
                '\n                Allowed and disallowed zone modifiers\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ZONE_MODIFIER),
                pack_safe=True)),
        'additional_drama_nodes':
        TunableList(
            description=
            '\n            A list of additional drama nodes that we will score and schedule\n            when this drama node is run.  Only 1 drama node is run.\n            ',
            tunable=TunableReference(
                description=
                '\n                A drama node that we will score and schedule when this drama\n                node is run.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.DRAMA_NODE)))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._duration_alarm_handle = None
        self._zone_id = None
        self._shown_notification = False
        self._additional_nodes_processor = None
        self._duration_override = None

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

    @classproperty
    def persist_when_active(cls):
        return True

    @classproperty
    def simless(cls):
        return True

    @property
    def zone_id(self):
        return self._zone_id

    @property
    def is_calendar_deletable(self):
        return False

    def get_calendar_end_time(self):
        return self.get_calendar_start_time() + create_time_span(
            minutes=self.duration)

    @property
    def zone_director_override(self):
        if services.current_zone_id() == self._zone_id:
            return self.zone_director

    def _setup(self, *args, zone_id=None, gsi_data=None, **kwargs):
        result = super()._setup(*args, gsi_data=gsi_data, **kwargs)
        if not result:
            return result
        else:
            self._zone_id = zone_id
            if self._zone_id is None:
                if gsi_data is not None:
                    gsi_data.rejected_nodes.append(
                        GSIRejectedDramaNodeScoringData(
                            type(self),
                            "Failed to setup drama node because it wasn't given a zone id to run in."
                        ))
                return False
        return True

    def cleanup(self, from_service_stop=False):
        super().cleanup(from_service_stop=from_service_stop)
        if self._duration_alarm_handle is not None:
            self._duration_alarm_handle.cancel()
            self._duration_alarm_handle = None

    def _test(self, *args, **kwargs):
        if self._zone_id is None:
            return TestResult(
                False,
                'Cannot run Venue Event Drama Node with no zone id set.')
        zone_modifiers = services.get_zone_modifier_service(
        ).get_zone_modifiers(self._zone_id)
        if not self.zone_modifier_requirements.test_collection(zone_modifiers):
            return TestResult(False,
                              'Incompatible zone modifiers tuned on venue.')
        return super()._test(*args, **kwargs)

    def _end_venue_behavior(self):
        if self.zone_director is not None:
            venue_service = services.venue_service()
            if type(venue_service.get_zone_director()) is self.zone_director:
                if self.ending_notification is not None:
                    dialog = self.ending_notification(
                        services.active_sim_info())
                    dialog.show_dialog()
                venue_service.change_zone_director(
                    venue_service.active_venue.zone_director(), True)
        elif self.ending_notification is not None:
            dialog = self.ending_notification(services.active_sim_info())
            dialog.show_dialog()

    def _show_notification(self):
        if self.notification is None:
            return
        if self._shown_notification:
            return
        dialog = self.notification(services.active_sim_info())
        dialog.show_dialog()
        self._shown_notification = True

    def _run_venue_behavior(self):
        services.venue_service().change_zone_director(self.zone_director(),
                                                      True)
        self._show_notification()

    def _resume_venue_behavior(self):
        self._show_notification()

    def _on_venue_event_complete(self, _):
        if services.current_zone_id() == self._zone_id:
            self._end_venue_behavior()
        services.drama_scheduler_service().complete_node(self._uid)

    def _show_away_notification(self):
        if self.away_notification is None:
            return
        zone_data = services.get_persistence_service().get_zone_proto_buff(
            self._zone_id)
        if zone_data is None:
            return
        venue_tuning_id = build_buy.get_current_venue(self._zone_id)
        venue_manager = services.get_instance_manager(
            sims4.resources.Types.VENUE)
        venue_tuning = venue_manager.get(venue_tuning_id)
        if venue_tuning is None:
            return
        dialog = self.away_notification(services.active_sim_info())
        dialog.show_dialog(additional_tokens=(zone_data.name,
                                              venue_tuning.display_name))

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

    def _validate_venue_tuning(self):
        venue_tuning_id = build_buy.get_current_venue(self._zone_id)
        venue_manager = services.get_instance_manager(
            sims4.resources.Types.VENUE)
        venue_tuning = venue_manager.get(venue_tuning_id)
        if venue_tuning is None:
            return False
        elif type(self) not in venue_tuning.drama_node_events:
            return False
        return True

    def _run(self):
        if not self._validate_venue_tuning():
            return DramaNodeRunOutcome.FAILURE
        self._duration_alarm_handle = alarms.add_alarm(
            self, create_time_span(minutes=self.duration),
            self._on_venue_event_complete)
        if services.current_zone_id() == self._zone_id:
            self._run_venue_behavior()
            return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE
        self._show_away_notification()
        if self.additional_drama_nodes:
            sim_timeline = services.time_service().sim_timeline
            self._additional_nodes_processor = sim_timeline.schedule(
                elements.GeneratorElement(self._process_scoring_gen))
        return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE

    def schedule_duration_alarm(self, callback, cross_zone=False):
        if self._duration_override is not None:
            time_span = TimeSpan(self._duration_override)
        else:
            time_span = create_time_span(minutes=self.duration)
        return alarms.add_alarm(self,
                                time_span,
                                callback,
                                cross_zone=cross_zone)

    def should_resume(self):
        return True

    def resume(self):
        if not self.should_resume():
            return
        if services.current_zone_id() == self._zone_id:
            self._resume_venue_behavior()
        self._duration_alarm_handle = self.schedule_duration_alarm(
            self._on_venue_event_complete)

    def _save_custom_data(self, writer):
        writer.write_uint64(ZONE_ID_TOKEN, self._zone_id)
        writer.write_bool(SHOWN_NOTIFICATION_TOKEN, self._shown_notification)
        if self._duration_alarm_handle is not None:
            writer.write_uint64(
                DURATION_TOKEN,
                int(self._duration_alarm_handle.get_remaining_time().in_ticks(
                )))

    def _load_custom_data(self, reader):
        self._zone_id = reader.read_uint64(ZONE_ID_TOKEN, None)
        if self._zone_id is None:
            return False
        self._shown_notification = reader.read_bool(SHOWN_NOTIFICATION_TOKEN,
                                                    False)
        self._duration_override = reader.read_uint64(DURATION_TOKEN, None)
        return True

    def travel_to_venue(self):
        active_sim_info = services.active_sim_info()
        active_sim = active_sim_info.get_sim_instance(
            allow_hidden_flags=ALL_HIDDEN_REASONS_EXCEPT_UNINITIALIZED)
        if active_sim is None:
            return
        if self._zone_id is None:
            logger.error('Failed to travel to venue')
            return
        zone = services.get_zone_manager().get(self._zone_id)
        if zone is None:
            logger.error(
                "The zone of the chosen venue event is not instanced. Travel to the drama node ({})s venue's zone failed.",
                str(self))
            return
        lot_id = zone.lot.lot_id
        if lot_id is None:
            logger.error(
                "Lot of the chosen zone is not instanced. Travel to the drama node ({})s venue's zone failed.",
                str(self))
            return
        pick = PickInfo(pick_type=PickType.PICK_TERRAIN,
                        lot_id=lot_id,
                        ignore_neighborhood_id=True)
        context = interactions.context.InteractionContext(
            active_sim,
            interactions.context.InteractionContext.
            SOURCE_SCRIPT_WITH_USER_INTENT,
            interactions.priority.Priority.High,
            insert_strategy=interactions.context.QueueInsertStrategy.NEXT,
            pick=pick)
        active_sim.push_super_affordance(
            VenueEventDramaNode.GO_TO_VENUE_ZONE_INTERACTION, None, context)

    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
        if not self._validate_venue_tuning():
            return False
        if self.ui_display_type != DramaNodeUiDisplayType.NO_UI:
            services.calendar_service().mark_on_calendar(self)
        return True

    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)
        return success

    def create_calendar_entry(self):
        calendar_entry = super().create_calendar_entry()
        calendar_entry.zone_id = self._zone_id
        build_icon_info_msg(
            IconInfoData(
                icon_resource=self._display_data.instance_display_icon),
            self._display_data.instance_display_name, calendar_entry.icon_info)
        calendar_entry.scoring_enabled = False
        return calendar_entry
class CivicInspectorSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {'inspector_job_and_role_state': TunableSituationJobAndRoleState(description='\n            The job and role state for the eco-inspector.\n            ', tuning_group=GroupNames.ROLES), 'inspector_entry': _InspectorEntryState.TunableFactory(description='\n           Inspector Entry State. Listens for portal allowance.\n            ', display_name='1. Inspector Entry State', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'civic_inspect_outside': _InspectorOutsideState.TunableFactory(description='\n           Inspecting from outside, not allowed inside.\n            ', display_name='2. Civic Inspect Outside', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'civic_inspect_inside': _InspectorInsideState.TunableFactory(description='\n            Allowed inside, but may inspect outside if not objects.\n            ', display_name='3. Civic Inspect Inside', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'final_checks': _FinalChecksState.TunableFactory(description='\n            Checks if civic policy is being followed using the conditions\n            described in tuning. \n            ', display_name='4. Final Checks', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'leave': _LeaveState.TunableFactory(description='\n            Final checks before leaving.\n            ', display_name='5. Leaving state.', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'inspection_successful_notification': UiDialogNotification.TunableFactory(description='\n            A TNS that is displayed to inform of successful inspection.\n            '), 'inspection_failure_notification': UiDialogNotification.TunableFactory(description='\n            A TNS that is displayed to inform of failed inspection.\n            '), 'inspection_partial_success_notification': UiDialogNotification.TunableFactory(description='\n            A TNS that is displayed to inform of failed inspection.\n            '), 'commodity_notfollow': Commodity.TunableReference(description='\n            lot commodity that we set when we want a multiplier for bill modifier.\n                '), 'commodity_follow': Commodity.TunableReference(description='\n            lot commodity that we set when we want a multiplier for bill modifier.\n                '), 'overlook_issues_success_interaction': TunableInteractionOfInterest(description='\n            Interaction pushed to indicate success of overlooking issues.\n            ')}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.follow_status = PolicyFollowing.FULL_FOLLOWED
        self.policies_not_followed = []

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _WaitState), SituationStateData(2, _InspectorEntryState, factory=cls.inspector_entry), SituationStateData(3, _InspectorInsideState, factory=cls.civic_inspect_inside), SituationStateData(4, _InspectorOutsideState, factory=cls.civic_inspect_outside), SituationStateData(5, _FinalChecksState, factory=cls.final_checks), SituationStateData(6, _LeaveState, factory=cls.leave))

    @classmethod
    def default_job(cls):
        pass

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

    def _on_add_sim_to_situation(self, sim, job_type, role_state_type_override=None):
        super()._on_add_sim_to_situation(sim, job_type, role_state_type_override=role_state_type_override)
        if self.inspector_person() is not None:
            self._change_state(self.inspector_entry())

    def _show_inspection_notification(self, inspection_notification, **kwargs):
        inspector = self.inspector_person()
        if inspector is not None:
            dialog = inspection_notification(inspector, resolver=SingleSimResolver(inspector.sim_info))
            dialog.show_dialog(**kwargs)

    def notify_result_and_push_bill_modifier(self):
        lot = services.active_lot()
        additional_tokens = ()
        if self.policies_not_followed:
            additional_tokens = (LocalizationHelperTuning.get_bulleted_list(None, *self.policies_not_followed),)
        if self.follow_status == PolicyFollowing.NOT_FOLLOWED:
            lot.set_stat_value(self.commodity_notfollow, self.commodity_notfollow.max_value_tuning)
            self._show_inspection_notification(self.inspection_failure_notification, additional_tokens=additional_tokens)
        elif self.follow_status == PolicyFollowing.PARTIAL_FOLLOWED:
            self._show_inspection_notification(self.inspection_partial_success_notification, additional_tokens=additional_tokens)
        else:
            lot.set_stat_value(self.commodity_follow, self.commodity_follow.max_value_tuning)
            self._show_inspection_notification(self.inspection_successful_notification)

    def inspector_person(self):
        sim = next(self.all_sims_in_job_gen(self.inspector_job_and_role_state.job), None)
        return sim

    def start_situation(self):
        super().start_situation()
        self._change_state(_WaitState())
示例#23
0
class Skill(HasTunableReference,
            statistics.continuous_statistic_tuning.TunedContinuousStatistic,
            metaclass=HashedTunedInstanceMetaclass,
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC)):
    __qualname__ = 'Skill'
    SKILL_LEVEL_LIST = TunableMapping(
        key_type=TunableEnumEntry(SkillLevelType, SkillLevelType.MAJOR),
        value_type=TunableList(
            Tunable(int, 0),
            description=
            'The level boundaries for skill type, specified as a delta from the previous value'
        ),
        export_modes=ExportModes.All)
    SKILL_EFFECTIVENESS_GAIN = TunableMapping(
        key_type=TunableEnumEntry(SkillEffectiveness,
                                  SkillEffectiveness.STANDARD),
        value_type=TunableCurve(),
        description='Skill gain points based on skill effectiveness.')
    DYNAMIC_SKILL_INTERVAL = TunableRange(
        description=
        '\n        Interval used when dynamic loot is used in a\n        PeriodicStatisticChangeElement.\n        ',
        tunable_type=float,
        default=1,
        minimum=1)
    INSTANCE_TUNABLES = {
        'stat_name':
        TunableLocalizedString(
            description=
            '\n            Localized name of this Statistic\n            ',
            export_modes=ExportModes.All),
        'ad_data':
        TunableList(
            description=
            '\n            A list of Vector2 points that define the desire curve for this\n            commodity.\n            ',
            tunable=TunableVector2(
                description=
                '\n                Point on a Curve\n                ',
                default=sims4.math.Vector2(0, 0))),
        'weight':
        Tunable(
            description=
            "\n            The weight of the Skill with regards to autonomy.  It's ignored \n            for the purposes of sorting stats, but it's applied when scoring \n            the actual statistic operation for the SI.\n            ",
            tunable_type=float,
            default=0.5),
        'skill_level_type':
        TunableEnumEntry(
            description='\n            Skill level list to use.\n            ',
            tunable_type=SkillLevelType,
            default=SkillLevelType.MAJOR,
            export_modes=ExportModes.All),
        'locked_description':
        TunableLocalizedString(
            description=
            "\n            The skill description when it's locked.\n            ",
            export_modes=ExportModes.All),
        'skill_description':
        TunableLocalizedString(
            description=
            "\n            The skill's normal description.\n            ",
            export_modes=ExportModes.All),
        'is_default':
        Tunable(
            description=
            '\n            Whether Sim will default has this skill.\n            ',
            tunable_type=bool,
            default=False),
        'genders':
        TunableSet(
            description=
            '\n            Skill allowed gender, empty set means not specified\n            ',
            tunable=TunableEnumEntry(tunable_type=sim_info_types.Gender,
                                     default=None,
                                     export_modes=ExportModes.All)),
        'ages':
        TunableSet(
            description=
            '\n            Skill allowed ages, empty set means not specified\n            ',
            tunable=TunableEnumEntry(tunable_type=sim_info_types.Age,
                                     default=None,
                                     export_modes=ExportModes.All)),
        'entitlement':
        TunableEntitlement(
            description=
            '\n            Entitlement required to use this skill.\n            '
        ),
        'icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed for the Skill.\n            ',
            default='PNG:missing_image',
            resource_types=sims4.resources.CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'tags':
        TunableList(
            description=
            '\n            The associated categories of the skill\n            ',
            tunable=TunableEnumEntry(tunable_type=tag.Tag,
                                     default=tag.Tag.INVALID)),
        'priority':
        Tunable(
            description=
            '\n            Skill priority.  Higher priority skill will trump other skills when\n            being displayed on the UI side. When a sim gains multiple skills at\n            the same time only the highest priority one will display a progress\n            bar over its head.\n            ',
            tunable_type=int,
            default=1,
            export_modes=ExportModes.All),
        'statistic_multipliers':
        TunableMapping(
            description=
            '\n            Multipliers this skill applies to other statistics based on its\n            value.\n            ',
            key_type=TunableReference(
                description=
                '\n                The statistic this multiplier will be applied to.\n                ',
                manager=services.statistic_manager(),
                reload_dependent=True),
            value_type=TunableTuple(
                curve=TunableCurve(
                    description=
                    '\n                    Tunable curve where the X-axis defines the skill level, and\n                    the Y-axis defines the associated multiplier.\n                    ',
                    x_axis_name='Skill Level',
                    y_axis_name='Multiplier'),
                direction=TunableEnumEntry(
                    description=
                    "\n                    Direction where the multiplier should work on the\n                    statistic.  For example, a tuned decrease for an object's\n                    brokenness rate will not also increase the time it takes to\n                    repair it.\n                    ",
                    tunable_type=StatisticChangeDirection,
                    default=StatisticChangeDirection.INCREASE),
                use_effective_skill=Tunable(
                    description=
                    '\n                    If checked, this modifier will look at the current\n                    effective skill value.  If unchecked, this modifier will\n                    look at the actual skill value.\n                    ',
                    tunable_type=bool,
                    needs_tuning=True,
                    default=True)),
            tuning_group=GroupNames.MULTIPLIERS),
        'success_chance_multipliers':
        TunableList(
            description=
            '\n            Multipliers this skill applies to the success chance of\n            affordances.\n            ',
            tunable=TunableSkillMultiplier(),
            tuning_group=GroupNames.MULTIPLIERS),
        'monetary_payout_multipliers':
        TunableList(
            description=
            '\n            Multipliers this skill applies to the monetary payout amount of\n            affordances.\n            ',
            tunable=TunableSkillMultiplier(),
            tuning_group=GroupNames.MULTIPLIERS),
        'next_level_teaser':
        TunableList(
            description=
            '\n            Tooltip which describes what the next level entails.\n            ',
            tunable=TunableLocalizedString(),
            export_modes=(ExportModes.ClientBinary, )),
        'level_data':
        TunableMapping(
            description=
            '\n            Level-specific information, such as notifications to be displayed to\n            level up.\n            ',
            key_type=int,
            value_type=TunableTuple(
                level_up_notification=UiDialogNotification.TunableFactory(
                    description=
                    '\n                    The notification to display when the Sim obtains this level.\n                    The text will be provided two tokens: the Sim owning the\n                    skill and a number representing the 1-based skill level\n                    ',
                    locked_args={
                        'text_tokens':
                        DEFAULT,
                        'icon':
                        None,
                        'primary_icon_response':
                        UiDialogResponse(text=None,
                                         ui_request=UiDialogResponse.
                                         UiDialogUiRequest.SHOW_SKILL_PANEL),
                        'secondary_icon':
                        None
                    }),
                level_up_screen_slam=OptionalTunable(
                    description=
                    '\n                    Screen slam to show when reaches this skill level.\n                    Localization Tokens: Sim - {0.SimFirstName}, Skill Name - \n                    {1.String}, Skill Number - {2.Number}\n                    ',
                    tunable=ui.screen_slam.TunableScreenSlamSnippet(),
                    tuning_group=GroupNames.UI))),
        'mood_id':
        TunableReference(
            description=
            '\n            When this mood is set and active sim matches mood, the UI will \n            display a special effect on the skill bar to represent that this \n            skill is getting a bonus because of the mood.\n            ',
            manager=services.mood_manager(),
            export_modes=ExportModes.All),
        'stat_asm_param':
        TunableStatAsmParam.TunableFactory(),
        'tutorial':
        TunableReference(
            description=
            '\n            Tutorial instance for this skill. This will be used to bring up the \n            skill lesson from the first notification for Sim to know this skill.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL),
            class_restrictions=('Tutorial', )),
        'skill_unlocks_on_max':
        TunableList(
            description=
            '\n            A list of skills that become unlocked when this skill is maxed.\n            ',
            tunable=TunableReference(
                description=
                '\n                A skill to unlock.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.STATISTIC),
                class_restrictions=('Skill', )))
    }
    REMOVE_INSTANCE_TUNABLES = ('min_value_tuning', 'max_value_tuning',
                                'decay_rate', '_default_convergence_value')

    def __init__(self, tracker):
        super().__init__(tracker, self.initial_value)
        self._delta_enabled = True
        self._callback_handle = None
        if self.tracker.owner.is_simulating:
            self.on_initial_startup()
        self._max_level_update_sent = False

    def on_initial_startup(self):
        if self.tracker.owner.is_selectable:
            self.refresh_level_up_callback()

    def on_remove(self, on_destroy=False):
        super().on_remove(on_destroy=on_destroy)
        self._destory_callback_handle()

    def _apply_multipliers_to_continuous_statistics(self):
        for stat in self.statistic_multipliers:
            while stat.continuous:
                owner_stat = self.tracker.get_statistic(stat)
                if owner_stat is not None:
                    owner_stat._recalculate_modified_decay_rate()

    @caches.cached
    def get_user_value(self):
        return super(Skill, self).get_user_value()

    def set_value(self,
                  value,
                  *args,
                  from_load=False,
                  interaction=None,
                  **kwargs):
        old_value = self.get_value()
        super().set_value(value, *args, **kwargs)
        self.get_user_value.cache.clear()
        if not from_load:
            new_value = self.get_value()
            new_level = self.convert_to_user_value(value)
            if old_value == self.initial_value and old_value != new_value:
                sim_info = self._tracker._owner
                services.get_event_manager().process_event(
                    test_events.TestEvent.SkillLevelChange,
                    sim_info=sim_info,
                    statistic=self.stat_type)
            old_level = self.convert_to_user_value(old_value)
            if old_level < new_level:
                self._apply_multipliers_to_continuous_statistics()

    def add_value(self, add_amount, interaction=None, **kwargs):
        old_value = self.get_value()
        if old_value == self.initial_value:
            telemhook = TELEMETRY_HOOK_SKILL_INTERACTION_FIRST_TIME
        else:
            telemhook = TELEMETRY_HOOK_SKILL_INTERACTION
        super().add_value(add_amount, interaction=interaction)
        self.get_user_value.cache.clear()
        if interaction is not None:
            self.on_skill_updated(telemhook, old_value, self.get_value(),
                                  interaction.affordance.__name__)

    def _update_value(self):
        old_value = self._value
        if gsi_handlers.sim_handlers_log.skill_change_archiver.enabled:
            last_update = self._last_update
        time_delta = super()._update_value()
        self.get_user_value.cache.clear()
        new_value = self._value
        if old_value == self.initial_value:
            telemhook = TELEMETRY_HOOK_SKILL_INTERACTION_FIRST_TIME
            self.on_skill_updated(telemhook, old_value, new_value,
                                  TELEMETRY_INTERACTION_NOT_AVAILABLE)
            sim_info = self._tracker._owner
            services.get_event_manager().process_event(
                test_events.TestEvent.SkillLevelChange,
                sim_info=sim_info,
                statistic=self.stat_type)
        old_level = self.convert_to_user_value(old_value)
        new_level = self.convert_to_user_value(new_value)
        if gsi_handlers.sim_handlers_log.skill_change_archiver.enabled and self.tracker.owner.is_sim:
            gsi_handlers.sim_handlers_log.archive_skill_change(
                self.tracker.owner, self, time_delta, old_value, new_value,
                new_level, last_update)
        if old_value < new_value and old_level < new_level:
            if self._tracker is not None:
                self._tracker.notify_watchers(self.stat_type, self._value,
                                              self._value)

    def on_skill_updated(self, telemhook, old_value, new_value,
                         affordance_name):
        owner_sim = self._tracker._owner
        if owner_sim.is_selectable:
            with telemetry_helper.begin_hook(skill_telemetry_writer,
                                             telemhook,
                                             sim=owner_sim) as hook:
                hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64)
                hook.write_string(TELEMETRY_FIELD_SKILL_AFFORDANCE,
                                  affordance_name)
                hook.write_bool(TELEMETRY_FIELD_SKILL_AFFORDANCE_SUCCESS, True)
                hook.write_int(TELEMETRY_FIELD_SKILL_AFFORDANCE_VALUE_ADD,
                               new_value - old_value)
        if old_value == self.initial_value:
            skill_level = self.convert_to_user_value(old_value)
            self._show_level_notification(skill_level)

    def _destory_callback_handle(self):
        if self._callback_handle is not None:
            self.remove_callback(self._callback_handle)
            self._callback_handle = None

    def refresh_level_up_callback(self):
        self._destory_callback_handle()

        def _on_level_up_callback(stat_inst):
            new_level = stat_inst.get_user_value()
            old_level = new_level - 1
            stat_inst.on_skill_level_up(old_level, new_level)
            stat_inst.refresh_level_up_callback()

        self._callback_handle = self.add_callback(
            Threshold(self._get_next_level_bound(), operator.ge),
            _on_level_up_callback)

    def on_skill_level_up(self, old_level, new_level):
        tracker = self.tracker
        sim_info = tracker._owner
        if self.reached_max_level:
            for skill in self.skill_unlocks_on_max:
                skill_instance = tracker.add_statistic(skill, force_add=True)
                skill_instance.set_value(skill.initial_value)
        with telemetry_helper.begin_hook(skill_telemetry_writer,
                                         TELEMETRY_HOOK_SKILL_LEVEL_UP,
                                         sim=sim_info) as hook:
            hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64)
            hook.write_int(TELEMETRY_FIELD_SKILL_LEVEL, new_level)
        if sim_info.account is not None:
            services.social_service.post_skill_message(sim_info, self,
                                                       old_level, new_level)
        self._show_level_notification(new_level)
        services.get_event_manager().process_event(
            test_events.TestEvent.SkillLevelChange,
            sim_info=sim_info,
            statistic=self.stat_type)

    def _show_level_notification(self, skill_level):
        sim_info = self._tracker._owner
        if not sim_info.is_npc:
            level_data = self.level_data.get(skill_level)
            if level_data is not None:
                tutorial_id = None
                if self.tutorial is not None and skill_level == 1:
                    tutorial_id = self.tutorial.guid64
                notification = level_data.level_up_notification(
                    sim_info, resolver=SingleSimResolver(sim_info))
                notification.show_dialog(icon_override=(self.icon, None),
                                         secondary_icon_override=(None,
                                                                  sim_info),
                                         additional_tokens=(skill_level, ),
                                         tutorial_id=tutorial_id)
                if level_data.level_up_screen_slam is not None:
                    level_data.level_up_screen_slam.send_screen_slam_message(
                        sim_info, sim_info, self.stat_name, skill_level)

    @classproperty
    def skill_type(cls):
        return cls

    @classproperty
    def remove_on_convergence(cls):
        return False

    @classmethod
    def can_add(cls, owner, force_add=False, **kwargs):
        if force_add:
            return True
        if cls.genders and owner.gender not in cls.genders:
            return False
        if cls.ages and owner.age not in cls.ages:
            return False
        if cls.entitlement is None:
            return True
        if owner.is_npc:
            return False
        return mtx.has_entitlement(cls.entitlement)

    @classmethod
    def get_level_list(cls):
        return cls.SKILL_LEVEL_LIST.get(cls.skill_level_type)

    @classmethod
    def get_max_skill_value(cls):
        level_list = cls.get_level_list()
        return sum(level_list)

    @classmethod
    def get_skill_value_for_level(cls, level):
        level_list = cls.get_level_list()
        if level > len(level_list):
            logger.error('Level {} out of bounds', level)
            return 0
        return sum(level_list[:level])

    @classmethod
    def get_skill_effectiveness_points_gain(cls, effectiveness_level, level):
        skill_gain_curve = cls.SKILL_EFFECTIVENESS_GAIN.get(
            effectiveness_level)
        if skill_gain_curve is not None:
            return skill_gain_curve.get(level)
        logger.error('{} does not exist in SKILL_EFFECTIVENESS_GAIN mapping',
                     effectiveness_level)
        return 0

    @classmethod
    def _tuning_loaded_callback(cls):
        super()._tuning_loaded_callback()
        level_list = cls.get_level_list()
        cls.max_level = len(level_list)
        cls.min_value_tuning = 0
        cls.max_value_tuning = sum(level_list)
        cls._default_convergence_value = cls.min_value_tuning
        cls._build_utility_curve_from_tuning_data(cls.ad_data)
        for stat in cls.statistic_multipliers:
            multiplier = cls.statistic_multipliers[stat]
            curve = multiplier.curve
            direction = multiplier.direction
            use_effective_skill = multiplier.use_effective_skill
            stat.add_skill_based_statistic_multiplier(cls, curve, direction,
                                                      use_effective_skill)
        for multiplier in cls.success_chance_multipliers:
            curve = multiplier.curve
            use_effective_skill = multiplier.use_effective_skill
            for affordance in multiplier.affordance_list:
                affordance.add_skill_multiplier(
                    affordance.success_chance_multipliers, cls, curve,
                    use_effective_skill)
        for multiplier in cls.monetary_payout_multipliers:
            curve = multiplier.curve
            use_effective_skill = multiplier.use_effective_skill
            for affordance in multiplier.affordance_list:
                affordance.add_skill_multiplier(
                    affordance.monetary_payout_multipliers, cls, curve,
                    use_effective_skill)

    @classmethod
    def _verify_tuning_callback(cls):
        success_multiplier_affordances = []
        for multiplier in cls.success_chance_multipliers:
            success_multiplier_affordances.extend(multiplier.affordance_list)
        if len(success_multiplier_affordances) != len(
                set(success_multiplier_affordances)):
            logger.error(
                "The same affordance has been tuned more than once under {}'s success multipliers, and they will overwrite each other. Please fix in tuning.",
                cls,
                owner='tastle')
        monetary_payout_multiplier_affordances = []
        for multiplier in cls.monetary_payout_multipliers:
            monetary_payout_multiplier_affordances.extend(
                multiplier.affordance_list)
        if len(monetary_payout_multiplier_affordances) != len(
                set(monetary_payout_multiplier_affordances)):
            logger.error(
                "The same affordance has been tuned more than once under {}'s monetary payout multipliers, and they will overwrite each other. Please fix in tuning.",
                cls,
                owner='tastle')

    @classmethod
    def convert_to_user_value(cls, value):
        if not cls.get_level_list():
            return 0
        current_value = value
        for (level, level_threshold) in enumerate(cls.get_level_list()):
            current_value -= level_threshold
            while current_value < 0:
                return level
        return level + 1

    @classmethod
    def convert_from_user_value(cls, user_value):
        (level_min, _) = cls._get_level_bounds(user_value)
        return level_min

    @classmethod
    def _get_level_bounds(cls, level):
        level_list = cls.get_level_list()
        level_min = sum(level_list[:level])
        if level < cls.max_level:
            level_max = sum(level_list[:level + 1])
        else:
            level_max = sum(level_list)
        return (level_min, level_max)

    def _get_next_level_bound(self):
        level = self.convert_to_user_value(self._value)
        (_, level_max) = self._get_level_bounds(level)
        return level_max

    @property
    def reached_max_level(self):
        max_value = self.get_max_skill_value()
        if self.get_value() >= max_value:
            return True
        return False

    @property
    def should_send_update(self):
        if not self.reached_max_level:
            return True
        if not self._max_level_update_sent:
            self._max_level_update_sent = True
            return True
        return False

    @classproperty
    def is_skill(cls):
        return True

    @classproperty
    def autonomy_weight(cls):
        return cls.weight

    @classmethod
    def create_skill_update_msg(cls, sim_id, stat_value):
        if not cls.convert_to_user_value(stat_value) > 0:
            return
        skill_msg = Commodities_pb2.Skill_Update()
        skill_msg.skill_id = cls.guid64
        skill_msg.curr_points = int(stat_value)
        skill_msg.sim_id = sim_id
        return skill_msg

    @property
    def is_initial_value(self):
        return self.initial_value == self.get_value()

    @classproperty
    def valid_for_stat_testing(cls):
        return True
示例#24
0
class HouseholdFundsInterestLootOp(BaseLootOperation):
    __qualname__ = 'HouseholdFundsInterestLootOp'
    FACTORY_TUNABLES = {'description': '\n            This loot will deliver interest income to the current Household for their current funds,\n            based on the percentage tuned against total held. \n        ', 'interest_rate': Tunable(description='\n            The percentage of interest to apply to current funds.\n            ', tunable_type=int, default=0), 'notification': OptionalTunable(description='\n            If enabled, this notification will display when this interest payment is made.\n            Token 0 is the Sim - i.e. {0.SimFirstName}\n            Token 1 is the interest payment amount - i.e. {1.Money}\n            ', tunable=UiDialogNotification.TunableFactory())}

    def __init__(self, interest_rate, notification, **kwargs):
        super().__init__(**kwargs)
        self._interest_rate = interest_rate
        self._notification = notification

    def _apply_to_subject_and_target(self, subject, target, resolver):
        pay_out = int(subject.household.funds.money*self._interest_rate*FLOAT_TO_PERCENT)
        subject.household.funds.add(pay_out, Consts_pb2.TELEMETRY_INTERACTION_REWARD, self._get_object_from_recipient(subject))
        if self._notification is not None:
            dialog = self._notification(subject, resolver)
            dialog.show_dialog(event_id=self._notification.factory.DIALOG_MSG_TYPE, additional_tokens=(pay_out,))
示例#25
0
class NotificationLootOp(BaseLootOperation):
    __qualname__ = 'NotificationLootOp'
    FACTORY_TUNABLES = {'description': '\n            This loot will display a notification to the screen.\n            ', 'notification': UiDialogNotification.TunableFactory(description='\n            This text will display in a notification pop up when completed.\n            ')}

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

    def _apply_to_subject_and_target(self, subject, target, resolver):
        if subject is not None and subject.is_selectable:
            dialog = self.notification(subject, resolver)
            dialog.show_dialog(event_id=self.notification.factory.DIALOG_MSG_TYPE)
示例#26
0
class RebateManager:
    REBATES = TunableMapping(
        description=
        '\n        A mapping of all rebate items to rebate data.\n        ',
        key_type=TunableEnumEntry(tunable_type=RebateItem,
                                  default=RebateItem.INVALID),
        value_type=RebateData())
    REBATE_PAYMENT_SCHEDULE = WeeklySchedule.TunableFactory(
        description=
        '\n        The schedule when accrued rebates will be paid out.\n        '
    )
    REBATE_CYCLE = WeeklySchedule.TunableFactory(
        description=
        '\n        The day of the week at which objects that qualify for cyclical rebates\n        will get tested back in and added to rebate objects.\n        '
    )
    REBATE_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        "\n        The notification that will show when the player receives their rebate\n        money.\n        \n        The notification's text is provided two tokens:\n         0 - An integer representing the total rebate amount \n         \n         1 - A string. The contents of the string are a bulleted list of the\n         entries specified for each of the traits.\n         \n        e.g.:\n         A rebate check of {0.Money} has been received! Sims in the household\n         were able to save on their recent purchases:\n{1.String}\n        "
    )

    def __init__(self, household):
        self._household = household
        self._rebates = Counter()
        self._rebate_object_ids = defaultdict(list)
        self._schedule = None
        self._rebate_cycle = None

    def register_tests_for_rebates(self, obj, rebate_item):
        if rebate_item.rebate_category.rebate_category_enum == RebateCategoryEnum.GAMEPLAY_OBJECT:
            obj.register_rebate_tests(rebate_item.tests_set)

    def add_rebate_for_object(self, obj_id, category):
        object_manager = services.object_manager()
        for (rebate_item, rebate_data) in self.REBATES.items():
            if rebate_data.rebate_category.rebate_category_enum != category:
                continue
            obj = object_manager.get(obj_id)
            if obj is None:
                return
            valid_objects = rebate_data.valid_objects
            if valid_objects is not None and not obj.has_any_tag(
                    valid_objects):
                continue
            if self.run_tests_on_object_and_household_sims(
                    obj, rebate_data.tests_set):
                self._rebates[
                    rebate_item] += rebate_data.rebate_payout_type if type(
                        rebate_data.rebate_payout_type
                    ) is int else obj.catalog_value * rebate_data.rebate_payout_type
                self._rebate_object_ids[rebate_item] = self._rebate_object_ids[
                    rebate_item] + [obj_id]
            else:
                self.register_tests_for_rebates(obj, rebate_data)
        if self._rebates:
            self.start_rebate_schedule()

    def add_rebate_for_object_from_cycle(self, scheduler, alarm_data,
                                         extra_data):
        obj_id = extra_data.get('obj_id')
        category = extra_data.get('category')
        self.add_rebate_for_object(obj_id, category)

    def add_rebate_cycle(self):
        cyclical_rebate_object_ids = set()
        for (rebate_item,
             rebate_object_id_list) in self._rebate_object_ids.items():
            rebate_data = self.REBATES.get(rebate_item)
            if not rebate_data.rebate_category.cyclical:
                continue
            for rebate_object_id in rebate_object_id_list:
                cyclical_rebate_object_ids.add(
                    (rebate_object_id, rebate_data.rebate_category))
        for (cyclical_object_id,
             rebate_category) in cyclical_rebate_object_ids:
            if self._rebate_cycle is None:
                self._rebate_cycle = self.REBATE_CYCLE(
                    start_callback=self.add_rebate_for_object_from_cycle,
                    extra_data={
                        'obj_id': cyclical_object_id,
                        'category': rebate_category
                    },
                    schedule_immediate=False)
            else:
                self._rebate_cycle.merge_schedule(
                    self.REBATE_CYCLE(
                        start_callback=self.add_rebate_for_object_from_cycle,
                        extra_data={
                            'obj_id': cyclical_object_id,
                            'category': rebate_category
                        },
                        schedule_immediate=False))

    def run_tests_on_object_and_household_sims(self, obj, test_set):
        result = False
        for sim_info in self._household.sim_info_gen():
            sim_and_object_resolver = SingleActorAndObjectResolver(
                sim_info, obj, None)
            if test_set.run_tests(sim_and_object_resolver):
                result = True
        return result

    def clear_rebates(self):
        self._rebates.clear()
        self._rebate_object_ids.clear()

    def start_rebate_schedule(self):
        if self._schedule is None:
            self._schedule = self.REBATE_PAYMENT_SCHEDULE(
                start_callback=self.payout_rebates, schedule_immediate=False)

    def payout_rebates(self, *_):
        if not self._rebates:
            return
        rebate_reasons = []
        for rebate_item_enum in self._rebates.keys():
            rebate_data = self.REBATES.get(rebate_item_enum)
            rebate_reasons.append(
                rebate_data.notification_text(
                    rebate_data.rebate_payout_type
                    if type(rebate_data.rebate_payout_type) is int else
                    rebate_data.rebate_payout_type * 100))
        rebate_reasons_string = LocalizationHelperTuning.get_bulleted_list(
            None, *rebate_reasons)
        total_rebate_amount = sum(self._rebates.values())
        active_sim_info = services.active_sim_info()
        dialog = self.REBATE_NOTIFICATION(active_sim_info)
        dialog.show_dialog(additional_tokens=(total_rebate_amount,
                                              rebate_reasons_string))
        self._household.funds.add(
            total_rebate_amount,
            reason=Consts_pb2.TELEMETRY_MONEY_ASPIRATION_REWARD,
            sim=active_sim_info)
        self.add_rebate_cycle()
        self.clear_rebates()
示例#27
0
class FestivalContestSubmitElement(XevtTriggeredElement):
    FACTORY_TUNABLES = {
        'success_notification_by_rank':
        TunableList(
            description=
            '\n            Notifications displayed if submitted object is large enough to be ranked in\n            the contest. Index refers to the place that the player is in currently.\n            1st, 2nd, 3rd, etc.\n            ',
            tunable=UiDialogNotification.TunableFactory(),
            tuning_group=GroupNames.UI),
        'unranked_notification':
        OptionalTunable(
            description=
            '\n            If enabled, notification displayed if submitted object is not large enough to rank in\n            the contest. \n            ',
            tunable=TunableUiDialogNotificationSnippet(
                description=
                '\n                The notification that will appear when the submitted object does not rank.\n                '
            ),
            tuning_group=GroupNames.UI)
    }

    def _do_behavior(self):
        resolver = self.interaction.get_resolver()
        running_contests = services.drama_scheduler_service(
        ).get_running_nodes_by_drama_node_type(DramaNodeType.FESTIVAL)
        for contest in running_contests:
            if hasattr(contest, 'festival_contest_tuning'):
                if contest.festival_contest_tuning is None:
                    continue
                if contest.is_during_pre_festival():
                    continue
                obj = self.interaction.get_participant(
                    ParticipantType.PickedObject)
                if obj is None:
                    logger.error('{} does not have PickedObject participant',
                                 resolver)
                    return False
                sim = self.interaction.sim
                if sim is None:
                    logger.error('{} does not have sim participant', resolver)
                    return False
                return self._enter_object_into_contest(contest, sim, obj,
                                                       resolver)
        logger.error('{} no valid active Contest', resolver)
        return False

    def _enter_object_into_contest(self, contest, sim, obj, resolver):
        weight_statistic = contest.festival_contest_tuning._weight_statistic
        weight_tracker = obj.get_tracker(weight_statistic)
        if weight_tracker is None:
            logger.error('{} picked object does not have weight stat {}',
                         resolver, weight_statistic)
            return False
        if contest.festival_contest_tuning._destroy_object_on_submit and not self._destroy_object(
                contest, sim, obj, resolver):
            return False
        elif not self._add_score(contest, sim, obj, resolver):
            return False
        return True

    def _destroy_object(self, contest, sim, obj, resolver):
        obj.make_transient()
        return True

    def _add_score(self, contest, sim, obj, resolver):
        weight_statistic = contest.festival_contest_tuning._weight_statistic
        weight_tracker = obj.get_tracker(weight_statistic)
        if weight_tracker is None:
            logger.error('{} picked object does not have weight stat {}',
                         resolver, weight_statistic)
            return False
        rank = contest.add_score(sim.id, obj.id,
                                 weight_tracker.get_value(weight_statistic))
        if rank is not None:
            if rank >= len(self.success_notification_by_rank):
                return False
            notification = self.success_notification_by_rank[rank]
            dialog = notification(sim, target_sim_id=sim.id, resolver=resolver)
            dialog.show_dialog()
        elif self.unranked_notification is not None:
            dialog = self.unranked_notification(sim,
                                                target_sim_id=sim.id,
                                                resolver=resolver)
            dialog.show_dialog()
        return True
示例#28
0
class SituationGoal(SituationGoalDisplayMixin,
                    metaclass=HashedTunedInstanceMetaclass,
                    manager=services.get_instance_manager(
                        sims4.resources.Types.SITUATION_GOAL)):
    INSTANCE_SUBCLASSES_ONLY = True
    IS_TARGETED = False
    INSTANCE_TUNABLES = {
        '_pre_tests':
        TunableSituationGoalPreTestSet(
            description=
            '\n            A set of tests on the player sim and environment that all must\n            pass for the goal to be given to the player. e.g. Player Sim\n            has cooking skill level 7.\n            ',
            tuning_group=GroupNames.TESTS),
        '_post_tests':
        TunableSituationGoalPostTestSet(
            description=
            '\n            A set of tests that must all pass when the player satisfies the\n            goal_test for the goal to be consider completed. e.g. Player\n            has Drunk Buff when Kissing another sim at Night.\n            ',
            tuning_group=GroupNames.TESTS),
        '_cancel_on_travel':
        Tunable(
            description=
            '\n            If set, this situation goal will cancel (technically, complete\n            with score overridden to 0 so that situation score is not\n            progressed) if situation changes zone.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.TESTS),
        '_environment_pre_tests':
        TunableSituationGoalEnvironmentPreTestSet(
            description=
            '\n            A set of sim independent pre tests.\n            e.g. There are five desks.\n            ',
            tuning_group=GroupNames.TESTS),
        'role_tags':
        TunableSet(
            TunableEnumEntry(Tag, Tag.INVALID),
            description=
            '\n            This goal will only be given to Sims in SituationJobs or Role\n            States marked with one of these tags.\n            '
        ),
        '_cooldown':
        TunableSimMinute(
            description=
            '\n            The cooldown of this situation goal.  Goals that have been\n            completed will not be chosen again for the amount of time that\n            is tuned.\n            ',
            default=600,
            minimum=0),
        '_iterations':
        Tunable(
            description=
            '\n             Number of times the player must perform the action to complete the goal\n             ',
            tunable_type=int,
            default=1),
        '_score':
        Tunable(
            description=
            '\n            The number of points received for completing the goal.\n            ',
            tunable_type=int,
            default=10),
        'score_on_iteration_complete':
        OptionalTunable(
            description=
            '\n            If enabled then we will add an amount of score to the situation\n            with every iteration of the situation goal completing.\n            ',
            tunable=Tunable(
                description=
                '\n                An amount of score that should be applied when an iteration\n                completes.\n                ',
                tunable_type=int,
                default=10)),
        '_pre_goal_loot_list':
        TunableList(
            description=
            '\n            A list of pre-defined loot actions that will applied to every\n            sim in the situation when this situation goal is started.\n             \n            Do not use this loot list in an attempt to undo changes made by\n            the RoleStates to the sim. For example, do not attempt\n            to remove buffs or commodities added by the RoleState.\n            ',
            tunable=SituationGoalLootActions.TunableReference()),
        '_goal_loot_list':
        TunableList(
            description=
            '\n            A list of pre-defined loot actions that will applied to every\n            sim in the situation when this situation goal is completed.\n             \n            Do not use this loot list in an attempt to undo changes made by\n            the RoleStates to the sim. For example, do not attempt\n            to remove buffs or commodities added by the RoleState.\n            ',
            tunable=SituationGoalLootActions.TunableReference()),
        'noncancelable':
        Tunable(
            description=
            '\n            Checking this box will prevent the player from canceling this goal in the whim system.',
            tunable_type=bool,
            default=False),
        'time_limit':
        Tunable(
            description=
            '\n            Timeout (in Sim minutes) for Sim to complete this goal. The default state of 0 means\n            time is unlimited. If the goal is not completed in time, any tuned penalty loot is applied.',
            tunable_type=int,
            default=0),
        'penalty_loot_list':
        TunableList(
            description=
            '\n            A list of pre-defined loot actions that will applied to the Sim who fails\n            to complete this goal within the tuned time limit.\n            ',
            tunable=SituationGoalLootActions.TunableReference()),
        'goal_awarded_notification':
        OptionalTunable(
            description=
            '\n            If enabled, this goal will have a notification associated with it.\n            It is up to whatever system awards the goal (e.g. the Whim system)\n            to display the notification when necessary.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'goal_completion_notification':
        OptionalTunable(tunable=UiDialogNotification.TunableFactory(
            description=
            '\n                A TNS that will fire when this situation goal is completed.\n                '
        )),
        'goal_completion_notification_and_modal_target':
        OptionalTunable(
            description=
            '\n            If enabled then we will use the tuned situation job to pick a\n            random sim in the owning situation with that job to be the target\n            sim of the notification and modal dialog.\n            ',
            tunable=TunableReference(
                description=
                '\n                The situation job that will be used to find a sim in the owning\n                situation to be the target sim.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.SITUATION_JOB))),
        'audio_sting_on_complete':
        TunableResourceKey(
            description=
            '\n            The sound to play when this goal is completed.\n            ',
            resource_types=(sims4.resources.Types.PROPX, ),
            default=None,
            allow_none=True,
            tuning_group=GroupNames.AUDIO),
        'goal_completion_modal_dialog':
        OptionalTunable(tunable=UiDialogOk.TunableFactory(
            description=
            '\n                A modal dialog that will fire when this situation goal is\n                completed.\n                '
        )),
        'visible_minor_goal':
        Tunable(
            description=
            '\n            Whether or not this goal should be displayed in the minor goals\n            list if this goal is for a player facing situation.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.UI),
        'display_type':
        TunableEnumEntry(
            description=
            '\n            How this goal is presented in user-facing situations.\n            ',
            tunable_type=SituationGoalDisplayType,
            default=SituationGoalDisplayType.NORMAL,
            tuning_group=GroupNames.UI)
    }

    @classmethod
    def can_be_given_as_goal(cls, actor, situation, **kwargs):
        if actor is not None:
            resolver = event_testing.resolver.DataResolver(
                actor.sim_info, None)
            result = cls._pre_tests.run_tests(resolver)
            if not result:
                return result
        else:
            resolver = GlobalResolver()
        environment_test_result = cls._environment_pre_tests.run_tests(
            resolver)
        if not environment_test_result:
            return environment_test_result
        return TestResult.TRUE

    def __init__(self,
                 sim_info=None,
                 situation=None,
                 goal_id=0,
                 count=0,
                 locked=False,
                 completed_time=None,
                 secondary_sim_info=None,
                 **kwargs):
        self._sim_info = sim_info
        self._secondary_sim_info = secondary_sim_info
        self._situation = situation
        self.id = goal_id
        self._on_goal_completed_callbacks = CallableList()
        self._completed_time = completed_time
        self._count = count
        self._locked = locked
        self._score_override = None
        self._goal_status_override = None
        self._setup = False

    def setup(self):
        self._setup = True

    def destroy(self):
        self.decommision()
        self._sim_info = None
        self._situation = None

    def decommision(self):
        if self._setup:
            self._decommision()

    def _decommision(self):
        self._on_goal_completed_callbacks.clear()

    def create_seedling(self):
        actor_id = 0 if self._sim_info is None else self._sim_info.sim_id
        target_sim_info = self.get_required_target_sim_info()
        target_id = 0 if target_sim_info is None else target_sim_info.sim_id
        secondary_target_id = 0 if self._secondary_sim_info is None else self._secondary_sim_info.sim_id
        seedling = situations.situation_serialization.GoalSeedling(
            type(self), actor_id, target_id, secondary_target_id, self._count,
            self._locked, self._completed_time)
        return seedling

    def register_for_on_goal_completed_callback(self, listener):
        self._on_goal_completed_callbacks.append(listener)

    def unregister_for_on_goal_completed_callback(self, listener):
        self._on_goal_completed_callbacks.remove(listener)

    def get_gsi_name(self):
        if self._iterations <= 1:
            return self.__class__.__name__
        return '{} {}/{}'.format(self.__class__.__name__, self._count,
                                 self._iterations)

    def on_goal_offered(self):
        if self._situation is None:
            return
        for sim in self._situation.all_sims_in_situation_gen():
            resolver = sim.get_resolver()
            for loots in self._pre_goal_loot_list:
                for loot in loots.goal_loot_actions:
                    loot.apply_to_resolver(resolver)

    def _display_goal_completed_dialogs(self):
        actor_sim_info = services.active_sim_info()
        target_sim_info = None
        if self.goal_completion_notification_and_modal_target is not None:
            possible_sims = list(
                self._situation.all_sims_in_job_gen(
                    self.goal_completion_notification_and_modal_target))
            if possible_sims:
                target_sim_info = random.choice(possible_sims)
            if target_sim_info is None:
                return
        resolver = DoubleSimResolver(actor_sim_info, target_sim_info)
        if self.goal_completion_notification is not None:
            notification = self.goal_completion_notification(actor_sim_info,
                                                             resolver=resolver)
            notification.show_dialog()
        if self.goal_completion_modal_dialog is not None:
            dialog = self.goal_completion_modal_dialog(actor_sim_info,
                                                       resolver=resolver)
            dialog.show_dialog()

    def _on_goal_completed(self, start_cooldown=True):
        if start_cooldown:
            self._completed_time = services.time_service().sim_now
        loot_sims = (self._sim_info, ) if self._situation is None else tuple(
            self._situation.all_sims_in_situation_gen())
        for loots in self._goal_loot_list:
            for loot in loots.goal_loot_actions:
                for sim in loot_sims:
                    loot.apply_to_resolver(sim.get_resolver())
        self._display_goal_completed_dialogs()
        with situations.situation_manager.DelayedSituationDestruction():
            self._on_goal_completed_callbacks(self, True)

    def _on_iteration_completed(self):
        self._on_goal_completed_callbacks(self, False)

    def force_complete(self,
                       target_sim=None,
                       score_override=None,
                       start_cooldown=True):
        self._score_override = score_override
        self._count = self._iterations
        self._on_goal_completed(start_cooldown=start_cooldown)

    def _valid_event_sim_of_interest(self, sim_info):
        return self._sim_info is None or self._sim_info is sim_info

    def handle_event(self, sim_info, event, resolver):
        if not self._valid_event_sim_of_interest(sim_info):
            return
        if self._run_goal_completion_tests(sim_info, event, resolver):
            self._count += 1
            if self._count >= self._iterations:
                self._on_goal_completed()
            else:
                self._on_iteration_completed()

    def _run_goal_completion_tests(self, sim_info, event, resolver):
        return self._post_tests.run_tests(resolver)

    def should_autocomplete_on_load(self, previous_zone_id):
        if self._cancel_on_travel:
            zone_id = services.current_zone_id()
            if previous_zone_id != zone_id:
                return True
        return False

    def get_actual_target_sim_info(self):
        pass

    @property
    def sim_info(self):
        return self._sim_info

    def get_required_target_sim_info(self):
        pass

    def get_secondary_sim_info(self):
        return self._secondary_sim_info

    @property
    def created_time(self):
        pass

    @property
    def completed_time(self):
        return self._completed_time

    def is_on_cooldown(self):
        if self._completed_time is None:
            return False
        time_since_last_completion = services.time_service(
        ).sim_now - self._completed_time
        return time_since_last_completion < interval_in_sim_minutes(
            self._cooldown)

    def get_localization_tokens(self):
        target_sim_info = self.get_required_target_sim_info()
        return (self._numerical_token, self._sim_info, target_sim_info,
                self._secondary_sim_info)

    def get_display_name(self):
        display_name = self.display_name
        if display_name is not None:
            return display_name(*self.get_localization_tokens())

    def get_display_tooltip(self):
        display_tooltip = self.display_tooltip
        if display_tooltip is not None:
            return display_tooltip(*self.get_localization_tokens())

    @property
    def score(self):
        if self._score_override is not None:
            return self._score_override
        return self._score

    @property
    def goal_status_override(self):
        return self._goal_status_override

    @property
    def completed_iterations(self):
        return self._count

    @property
    def max_iterations(self):
        return self._iterations

    @property
    def _numerical_token(self):
        return self.max_iterations

    @property
    def locked(self):
        return self._locked

    def toggle_locked_status(self):
        self._locked = not self._locked

    def validate_completion(self):
        if self._completed_time is not None:
            return
        if self.completed_iterations < self.max_iterations:
            return
        self.force_complete()

    def show_goal_awarded_notification(self):
        if self.goal_awarded_notification is None:
            return
        icon_override = IconInfoData(icon_resource=self.display_icon)
        secondary_icon_override = IconInfoData(obj_instance=self._sim_info)
        notification = self.goal_awarded_notification(self._sim_info)
        notification.show_dialog(
            additional_tokens=self.get_localization_tokens(),
            icon_override=icon_override,
            secondary_icon_override=secondary_icon_override)
class AspirationTrack(metaclass=HashedTunedInstanceMetaclass,
                      manager=services.get_instance_manager(
                          sims4.resources.Types.ASPIRATION_TRACK)):
    __qualname__ = 'AspirationTrack'
    INSTANCE_TUNABLES = {
        'display_text':
        sims4.localization.TunableLocalizedString(
            description=
            '\n            Text used to show the Aspiration Track name in the UI',
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'description_text':
        sims4.localization.TunableLocalizedString(
            description=
            '\n            Text used to show the Aspiration Track description in the UI',
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'icon':
        sims4.tuning.tunable.TunableResourceKey(
            None,
            resource_types=sims4.resources.CompoundTypes.IMAGE,
            description=
            '\n            The icon to be displayed in the panel view.\n            ',
            export_modes=sims4.tuning.tunable_base.ExportModes.All,
            tuning_group=GroupNames.UI),
        'icon_high_res':
        sims4.tuning.tunable.TunableResourceKey(
            None,
            resource_types=sims4.resources.CompoundTypes.IMAGE,
            description=
            '\n            The icon to be displayed in aspiration track selection.\n            ',
            export_modes=sims4.tuning.tunable_base.ExportModes.All,
            tuning_group=GroupNames.UI),
        'category':
        sims4.tuning.tunable.TunableReference(
            description=
            '\n            The category this aspiration track goes into when displayed in the UI.',
            manager=services.get_instance_manager(
                sims4.resources.Types.ASPIRATION_CATEGORY),
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'primary_trait':
        sims4.tuning.tunable.TunableReference(
            description=
            '\n            This is the primary Aspiration reward trait that is applied upon selection from CAS.',
            manager=services.get_instance_manager(sims4.resources.Types.TRAIT),
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'aspirations':
        sims4.tuning.tunable.TunableMapping(
            description=
            '\n            A Set of objectives for completing an aspiration.',
            key_type=TunableEnumEntry(AspirationTrackLevels,
                                      AspirationTrackLevels.LEVEL_1),
            value_type=sims4.tuning.tunable.TunableReference(
                description=
                '\n                One aspiration in the track, associated for a level',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ASPIRATION),
                class_restrictions='Aspiration',
                reload_dependent=True),
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'reward':
        sims4.tuning.tunable.TunableReference(
            description=
            '\n            Which rewards are given when this aspiration track is completed.',
            manager=services.get_instance_manager(
                sims4.resources.Types.REWARD),
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            This text will display in a notification pop up when completed.\n            ',
            locked_args={
                'text_tokens':
                DEFAULT,
                'icon':
                None,
                'primary_icon_response':
                UiDialogResponse(text=None,
                                 ui_request=UiDialogResponse.UiDialogUiRequest.
                                 SHOW_ASPIRATION_SELECTOR),
                'secondary_icon':
                None
            }),
        'mood_asm_param':
        sims4.tuning.tunable.Tunable(
            description=
            "\n            The asm parameter for Sim's mood for use with CAS ASM state machine, driven by selection\n            of this AspirationTrack, i.e. when a player selects the a romantic aspiration track, the Flirty\n            ASM is given to the state machine to play. The name tuned here must match the animation\n            state name parameter expected in Swing.",
            tunable_type=str,
            default=None,
            source_query=SourceQueries.SwingEnumNamePattern.format('mood'),
            export_modes=sims4.tuning.tunable_base.ExportModes.All)
    }
    _sorted_aspirations = None

    @classmethod
    def get_aspirations(cls):
        return cls._sorted_aspirations

    @classmethod
    def get_next_aspriation(cls, current_aspiration):
        next_aspiration_level = None
        current_aspiration_guid = current_aspiration.guid64
        for (level, track_aspiration) in cls.aspirations.items():
            while track_aspiration.guid64 == current_aspiration_guid:
                next_aspiration_level = int(level) + 1
                break
        if next_aspiration_level in cls.aspirations:
            return cls.aspirations[next_aspiration_level]

    @classproperty
    def is_child_aspiration_track(cls):
        return cls._sorted_aspirations[0][1].is_child_aspiration

    @classmethod
    def _tuning_loaded_callback(cls):
        cls._sorted_aspirations = tuple(sorted(cls.aspirations.items()))

    @classmethod
    def _verify_tuning_callback(cls):
        logger.debug('Loading asset: {}', cls, owner='ddriscoll')
        if cls.category == None:
            logger.error('{} Aspiration Track has no category set.',
                         cls,
                         owner='ddriscoll')
        if len(cls.aspirations) == 0:
            logger.error(
                '{} Aspiration Track has no aspirations mapped to levels.',
                cls,
                owner='ddriscoll')
        else:
            aspiration_list = cls.aspirations.values()
            aspiration_set = set(aspiration_list)
            if len(aspiration_set) != len(aspiration_list):
                logger.error(
                    '{} Aspiration Track has repeating aspiration values in the aspiration map.',
                    cls,
                    owner='ddriscoll')
class RebateManager:
    __qualname__ = 'RebateManager'
    TRAIT_REBATE_MAP = TunableMapping(
        description=
        '\n        A mapping of traits and the tags of objects which provide a rebate for\n        the given trait.\n        ',
        key_type=TunableReference(
            description=
            '\n            If the Sim has this trait, any objects purchased with the given\n            tag(s) below will provide a rebate.\n            ',
            manager=services.trait_manager()),
        value_type=TunableTuple(
            description=
            '\n            The information about the rebates the player should get for having\n            the mapped trait.\n            ',
            valid_objects=TunableVariant(
                description=
                '\n                The items to which the rebate will be applied.\n                ',
                by_tag=TunableSet(
                    description=
                    '\n                    The rebate will only be applied to objects purchased with the\n                    tags in this list.\n                    ',
                    tunable=TunableEnumEntry(tag.Tag, tag.Tag.INVALID)),
                locked_args={'all_purchases': None}),
            rebate_percentage=TunablePercent(
                description=
                '\n                The percentage of the catalog price that the player will get\n                back in the rebate.\n                ',
                default=10)))
    REBATE_PAYMENT_SCHEDULE = TunableWeeklyScheduleFactory(
        description=
        '\n        The schedule when accrued rebates will be paid out.\n        '
    )
    REBATE_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        The notification that will show when the player receives their rebate money.\n        ',
        locked_args={'text': None})
    REBATE_NOTIFICATION_HEADER = TunableLocalizedString(
        description=
        '\n        The header for the rebate notification that displays when the households\n        gets their rebate payout.\n        '
    )
    REBATE_NOTIFICATION_LINE_ITEM = TunableLocalizedStringFactory(
        description=
        '\n        Each trait that gave rebates will generate a new line item on the notification.\n        {0.String} = trait name\n        {1.Money} = amount of rebate money received from the trait.\n        '
    )

    def __init__(self, household):
        self._household = household
        self._rebates = Counter()
        self._schedule = None

    def add_rebate_for_object(self, obj):
        for (trait, rebate_info) in self.TRAIT_REBATE_MAP.items():
            rebate_percentage = rebate_info.rebate_percentage
            valid_objects = rebate_info.valid_objects
            while self._sim_in_household_has_trait(trait):
                if valid_objects is None or self._object_has_required_tags(
                        obj, valid_objects):
                    self._rebates[
                        trait] += obj.catalog_value * rebate_percentage
        if self._rebates:
            self.start_rebate_schedule()

    def _sim_in_household_has_trait(self, trait):
        return any(
            s.trait_tracker.has_trait(trait)
            for s in self._household.sim_info_gen())

    @staticmethod
    def _object_has_required_tags(obj, valid_tags):
        return set(obj.tags) & set(valid_tags)

    def clear_rebates(self):
        self._rebates.clear()

    def start_rebate_schedule(self):
        if self._schedule is None:
            self._schedule = self.REBATE_PAYMENT_SCHEDULE(
                start_callback=self._payout_rebates, schedule_immediate=False)

    def _payout_rebates(self, *_):
        if not self._rebates:
            return
        active_sim = self._household.client.active_sim
        line_item_text = LocalizationHelperTuning.get_new_line_separated_strings(
            *(self.REBATE_NOTIFICATION_LINE_ITEM(t.display_name(active_sim), a)
              for (t, a) in self._rebates.items()))
        notification_text = LocalizationHelperTuning.get_new_line_separated_strings(
            self.REBATE_NOTIFICATION_HEADER, line_item_text)
        dialog = self.REBATE_NOTIFICATION(
            active_sim, text=lambda *_, **__: notification_text)
        dialog.show_dialog()
        total_rebate_amount = sum(self._rebates.values())
        self._household.funds.add(
            total_rebate_amount,
            reason=Consts_pb2.TELEMETRY_MONEY_ASPIRATION_REWARD,
            sim=active_sim)
        self.clear_rebates()