class ZoneModifierDisplayInfo(HasTunableReference,
                              metaclass=HashedTunedInstanceMetaclass,
                              manager=services.get_instance_manager(
                                  Types.USER_INTERFACE_INFO)):
    base_game_only = True
    INSTANCE_TUNABLES = {
        'zone_modifier_icon':
        TunableIcon(
            description="\n            The zone modifier's icon.\n            ",
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI),
        'zone_modifier_name':
        TunableLocalizedString(
            description="\n            The zone modifier's name.\n            ",
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI),
        'zone_modifier_description':
        TunableLocalizedString(
            description=
            "\n            The zone modifier's description.\n            ",
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI),
        'zone_modifier_reference':
        TunablePackSafeReference(
            description=
            '\n            The zone modifier gameplay tuning reference ID.\n            \n            This ID will be what is persisted in save data and used\n            for any lookups.\n            ',
            manager=services.zone_modifier_manager(),
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI)
    }
Example #2
0
 def __init__(self, description='', **kwargs):
     super().__init__(
         default_icon=TunableIcon(
             description=
             '\n                Icon to display for this menu item.\n                '
         ),
         selected_icon=TunableIcon(
             description=
             '\n                Icon to use for the selected state of the item.\n                If not specified, will fall back to the default icon.\n                ',
             allow_none=True),
         new_content_icon=TunableIcon(
             description=
             '\n                Icon to use when there is new content related to this item.\n                If not specified, will fall back to the default icon.\n                ',
             allow_none=True),
         description=description,
         **kwargs)
Example #3
0
 def __init__(self, *args, **kwargs):
     super().__init__(
         *args,
         bg_image=TunableIcon(
             description=
             '\n                Image resource to display as UI phone panel background.\n                '
         ),
         icon=TunableIcon(
             description=
             '\n                Icon to display for phone color selector swatch.\n                '
         ),
         phone_trait=TunableReference(
             description=
             '\n                Trait associated with cell phone color.\n                ',
             allow_none=True,
             manager=services.get_instance_manager(
                 sims4.resources.Types.TRAIT)),
         **kwargs)
Example #4
0
class UiDialogLabeledIcons(UiDialogOk):
    FACTORY_TUNABLES = {
        'labeled_icons':
        TunableList(
            TunableTuple(
                description=
                '\n            A list of icons and labels to display in the UI dialog.\n            ',
                icon=TunableIcon(),
                label=TunableLocalizedStringFactory()))
    }

    def build_msg(self, additional_tokens=(), additional_icons=None, **kwargs):
        msg = super().build_msg(additional_tokens=additional_tokens, **kwargs)
        msg.dialog_type = Dialog_pb2.UiDialogMessage.ICONS_LABELS
        for labeled_icon in self.labeled_icons:
            msg.icon_infos.append(
                create_icon_info_msg(IconInfoData(labeled_icon.icon),
                                     name=self._build_localized_string_msg(
                                         labeled_icon.label,
                                         *additional_tokens)))
        if additional_icons:
            msg.icon_infos.extend(additional_icons)
        return msg
Example #5
0
class UniversityCourseData(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(Types.UNIVERSITY_COURSE_DATA)):
    INSTANCE_TUNABLES = {'spawn_point_tag': TunableMapping(description='\n            University specific spawn point tags.\n            Used by course related interactions to determine which spawn\n            point to use for the constraint. (i.e. the one in front of the\n            appropriate building)\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableSet(tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('Spawn',)), minlength=1)), 'classroom_tag': TunableMapping(description='\n            University specific classroom tags.\n            Used by university interactions on shells to determine which building\n            shell should have the interaction(s) available.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableSet(tunable=TunableEnumEntry(tunable_type=Tag, default=Tag.INVALID), minlength=1)), 'university_course_mapping': TunableMapping(description='\n            University specific course name and description.\n            Each university can have its own course name and description\n            defined.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableTuple(course_name=TunableLocalizedStringFactory(description='\n                    The name of this course.\n                    '), course_description=TunableLocalizedString(description='\n                    A description for this course.\n                    ', allow_none=True), export_class_name='UniversityCourseDisplayData'), tuple_name='UniversityCourseDataMapping', export_modes=ExportModes.All), 'course_skill_data': TunableTuple(description='\n            The related skill data for this specific course.  Whenever a Sim \n            does something that increases their course grade performance (like\n            attending lecture or studying), this skill will also increase by\n            the tunable amount.  Likewise, whenever this related skill \n            increases, the course grade will also increase.\n            ', related_skill=OptionalTunable(description='\n                The related skill associated with this course.\n                ', tunable=TunablePackSafeReference(manager=services.get_instance_manager(Types.STATISTIC), class_restrictions=('Skill',)))), 'icon': TunableIcon(description='\n            Icon for this university course.\n            ', export_modes=ExportModes.All, allow_none=True), 'cost': TunableRange(description='\n            The cost of this course.\n            ', tunable_type=int, default=200, minimum=0, export_modes=ExportModes.All), 'course_tags': TunableTags(description='\n            The tag for this course.  Used for objects that may be shared \n            between courses.\n            ', filter_prefixes=['course']), 'final_requirement_type': TunableEnumEntry(description='\n            The final requirement for this course.  This requirement must be \n            completed before the course can be considered complete.\n            ', tunable_type=FinalCourseRequirement, default=FinalCourseRequirement.NONE), 'final_requirement_aspiration': TunableReference(description='\n            An aspiration to use for tracking the final course requirement. \n            ', manager=services.get_instance_manager(sims4.resources.Types.ASPIRATION), class_restrictions='AspirationAssignment', allow_none=True), 'professor_assignment_trait': TunableMapping(description='\n            A mapping of University -> professor assignment trait.\n            \n            This is needed because each of the universities shipped with EP08\n            use the exact same classes but we want different teachers for each\n            university.\n            ', key_type=TunableReference(description='\n                A reference to the University that the professor will belong to.\n                ', manager=services.get_instance_manager(sims4.resources.Types.UNIVERSITY)), value_type=TunableReference(description='\n                The trait used to identify the professor for this course.\n                ', manager=services.get_instance_manager(sims4.resources.Types.TRAIT)))}

    @classproperty
    def is_elective(cls):
        return any(cls is e.elective for e in University.COURSE_ELECTIVES.electives)
Example #6
0
class Gig(HasTunableReference,
          _GigDisplayMixin,
          PrepTaskTrackerMixin,
          metaclass=HashedTunedInstanceMetaclass,
          manager=services.get_instance_manager(
              sims4.resources.Types.CAREER_GIG)):
    INSTANCE_TUNABLES = {
        'career':
        TunableReference(
            description=
            '\n            The career this gig is associated with.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.CAREER)),
        'gig_time':
        WeeklySchedule.TunableFactory(
            description=
            '\n            A tunable schedule that will determine when you have to be at work.\n            ',
            export_modes=ExportModes.All),
        'gig_prep_time':
        TunableTimeSpan(
            description=
            '\n            The amount of time between when a gig is selected and when it\n            occurs.\n            ',
            default_hours=5),
        'gig_prep_tasks':
        TunableList(
            description=
            '\n            A list of prep tasks the Sim can do to improve their performance\n            during the gig. \n            ',
            tunable=PrepTask.TunableFactory()),
        'loots_on_schedule':
        TunableList(
            description=
            '\n            Loot actions to apply when a sim gets a gig.\n            ',
            tunable=LootActions.TunableReference()),
        'audio_on_prep_task_completion':
        OptionalTunable(
            description=
            '\n            A sting to play at the time a prep task completes.\n            ',
            tunable=TunablePlayAudio(
                locked_args={
                    'immediate_audio': True,
                    'joint_name_hash': None,
                    'play_on_active_sim_only': True
                })),
        'gig_pay':
        TunableVariant(
            description=
            '\n            Base amount of pay for this gig. Can be either a flat amount or a\n            range.\n            ',
            range=TunableInterval(tunable_type=int,
                                  default_lower=0,
                                  default_upper=100,
                                  minimum=0),
            flat_amount=TunableIntervalLiteral(tunable_type=int,
                                               default=0,
                                               minimum=0),
            default='range'),
        'additional_pay_per_overmax_level':
        OptionalTunable(
            description=
            '\n            If checked, overmax levels will be considered when calculating pay\n            for this gig. The actual implementation of this may vary by gig\n            type.\n            ',
            tunable=TunableRange(tunable_type=int, default=0, minimum=0)),
        'result_based_gig_pay_multipliers':
        OptionalTunable(
            description=
            '\n            A set of multipliers for gig pay. The multiplier used depends on the\n            GigResult of the gig. The meanings of each GigResult may vary by\n            gig type.\n            ',
            tunable=TunableMapping(
                description=
                '\n                A map between the result type of the gig and the additional pay\n                the sim will receive.\n                ',
                key_type=TunableEnumEntry(tunable_type=GigResult,
                                          default=GigResult.SUCCESS),
                value_type=TunableMultiplier.TunableFactory())),
        'initial_result_based_career_performance':
        OptionalTunable(
            description=
            "\n            A mapping between the GigResult for this gig and the initial\n            career performance for the Sim's first gig.\n            ",
            tunable=TunableMapping(
                description=
                '\n                A map between the result type of the gig and the initial career\n                performance the Sim will receive.\n                ',
                key_type=TunableEnumEntry(
                    description=
                    '\n                    The GigResult enum that represents the outcome of the Gig.\n                    ',
                    tunable_type=GigResult,
                    default=GigResult.SUCCESS),
                value_type=Tunable(
                    description=
                    '\n                    The initial performance value that will be applied.\n                    ',
                    tunable_type=float,
                    default=0))),
        'result_based_career_performance':
        OptionalTunable(
            description=
            '\n            A mapping between the GigResult for this gig and the change in\n            career performance for the sim.\n            ',
            tunable=TunableMapping(
                description=
                '\n                A map between the result type of the gig and the career\n                performance the sim will receive.\n                ',
                key_type=TunableEnumEntry(
                    description=
                    '\n                    The GigResult enum that represents the outcome of the Gig.\n                    ',
                    tunable_type=GigResult,
                    default=GigResult.SUCCESS),
                value_type=Tunable(
                    description=
                    '\n                    The performance modification.\n                    ',
                    tunable_type=float,
                    default=0))),
        'result_based_career_performance_multiplier':
        OptionalTunable(
            description=
            '\n            A mapping between the GigResult and the multiplier for the career \n            performance awarded.\n            ',
            tunable=TunableMapping(
                description=
                '\n                A map between the result type of the gig and the career\n                performance multiplier.\n                ',
                key_type=TunableEnumEntry(
                    description=
                    '\n                    The GigResult enum that represents the outcome of the Gig.\n                    ',
                    tunable_type=GigResult,
                    default=GigResult.SUCCESS),
                value_type=TunableMultiplier.TunableFactory(
                    description=
                    '\n                    The performance modification multiplier.\n                    '
                ))),
        'result_based_loots':
        OptionalTunable(
            description=
            '\n            A mapping between the GigResult for this gig and a loot list to\n            optionally apply. The resolver for this loot list is either a\n            SingleSimResolver of the working sim or a DoubleSimResolver with the\n            target being the customer if there is a customer sim.\n            ',
            tunable=TunableMapping(
                description=
                '\n                A map between the result type of the gig and the loot list.\n                ',
                key_type=TunableEnumEntry(tunable_type=GigResult,
                                          default=GigResult.SUCCESS),
                value_type=TunableList(
                    description=
                    '\n                    Loot actions to apply.\n                    ',
                    tunable=LootActions.TunableReference(
                        description=
                        '\n                        The loot action applied.\n                        ',
                        pack_safe=True)))),
        'payout_stat_data':
        TunableMapping(
            description=
            '\n            Stats, and its associated information, that are gained (or lost) \n            when sim finishes this gig.\n            ',
            key_type=TunableReference(
                description=
                '\n                Stat for this payout.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.STATISTIC)),
            value_type=TunableTuple(
                description=
                '\n                Data about this payout stat. \n                ',
                base_amount=Tunable(
                    description=
                    '\n                    Base amount (pre-modifiers) applied to the sim at the end\n                    of the gig.\n                    ',
                    tunable_type=float,
                    default=0.0),
                medal_to_payout=TunableMapping(
                    description=
                    '\n                    Mapping of medal -> stat multiplier.\n                    ',
                    key_type=TunableEnumEntry(
                        description=
                        '\n                        Medal achieved in this gig.\n                        ',
                        tunable_type=SituationMedal,
                        default=SituationMedal.TIN),
                    value_type=TunableMultiplier.TunableFactory(
                        description=
                        '\n                        Mulitiplier on statistic payout if scorable situation\n                        ends with the associate medal.\n                        '
                    )),
                ui_threshold=TunableList(
                    description=
                    '\n                    Thresholds and icons we use for this stat to display in \n                    the end of day dialog. Tune in reverse of highest threshold \n                    to lowest threshold.\n                    ',
                    tunable=TunableTuple(
                        description=
                        '\n                        Threshold and icon for this stat and this gig.\n                        ',
                        threshold_icon=TunableIcon(
                            description=
                            '\n                            Icon if the stat is of this threshold.\n                            '
                        ),
                        threshold_description=TunableLocalizedStringFactory(
                            description=
                            '\n                            Description to use with icon\n                            '
                        ),
                        threshold=Tunable(
                            description=
                            '\n                            Threshold that the stat must >= to.\n                            ',
                            tunable_type=float,
                            default=0.0))))),
        'career_events':
        TunableList(
            description=
            '\n             A list of available career events for this gig.\n             ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.CAREER_EVENT))),
        'gig_cast_rel_bit_collection_id':
        TunableEnumEntry(
            description=
            '\n            If a rel bit is applied to the cast member, it must be of this collection id.\n            We use this to clear the rel bit when the gig is over.\n            ',
            tunable_type=RelationshipBitCollectionUid,
            default=RelationshipBitCollectionUid.Invalid,
            invalid_enums=(RelationshipBitCollectionUid.All, )),
        'gig_cast':
        TunableList(
            description=
            '\n            This is the list of sims that need to spawn for this gig. \n            ',
            tunable=TunableTuple(
                description=
                '\n                Data for cast members. It contains a test which tests against \n                the owner of this gig and spawn the necessary sims. A bit\n                may be applied through the loot action to determine the type \n                of cast members (costars, directors, etc...) \n                ',
                filter_test=TunableTestSet(
                    description=
                    '\n                    Test used on owner sim.\n                    '
                ),
                sim_filter=TunableSimFilter.TunableReference(
                    description=
                    '\n                    If filter test is passed, this sim is created and stored.\n                    '
                ),
                cast_member_rel_bit=OptionalTunable(
                    description=
                    '\n                    If tuned, this rel bit will be applied on the spawned cast \n                    member.\n                    ',
                    tunable=RelationshipBit.TunableReference(
                        description=
                        '\n                        Rel bit to apply.\n                        '
                    )))),
        'end_of_gig_dialog':
        OptionalTunable(
            description=
            '\n            A results dialog to show. This dialog allows a list\n            of icons with labels. Stats are added at the end of this icons.\n            ',
            tunable=UiDialogLabeledIcons.TunableFactory()),
        'disabled_tooltip':
        OptionalTunable(
            description=
            '\n            If tuned, the tooltip when this row is disabled.\n            ',
            tunable=TunableLocalizedStringFactory(),
            tuning_group=GroupNames.UI),
        'end_of_gig_notifications':
        OptionalTunable(
            description=
            '\n            If enabled, a notification to show at the end of the gig instead of\n            a normal career message. Tokens are:\n            * 0: The Sim owner of the career\n            * 1: The level name (e.g. Chef)\n            * 2: The career name (e.g. Culinary)\n            * 3: The company name (e.g. Maids United)\n            * 4: The pay for the gig\n            * 5: The gratuity for the gig\n            * 6: The customer (sim) of the gig, if there is a customer.\n            * 7: A bullet list of loots and payments as a result of this gig.\n                 This list uses the text tuned on the loots themselves to create\n                 bullets for each loot. Those texts will generally have tokens 0\n                 and 1 be the subject and target sims (of the loot) but may\n                 have additional tokens depending on the type of loot.\n            ',
            tunable=TunableMapping(
                description=
                '\n                A map between the result type of the gig and the post-gig\n                notification.\n                ',
                key_type=TunableEnumEntry(tunable_type=GigResult,
                                          default=GigResult.SUCCESS),
                value_type=_get_career_notification_tunable_factory()),
            tuning_group=GroupNames.UI),
        'end_of_gig_overmax_notification':
        OptionalTunable(
            description=
            '\n            If tuned, the notification that will be used if the sim gains an\n            overmax level during this gig. Will override the overmax\n            notification in career messages. The following tokens are provided:\n            * 0: The Sim owner of the career\n            * 1: The level name (e.g. Chef)\n            * 2: The career name (e.g. Culinary)\n            * 3: The company name (e.g. Maids United)\n            * 4: The overmax level\n            * 5: The pay for the gig\n            * 6: Additional pay tuned at additional_pay_per_overmax_level \n            * 7: The overmax rewards in a bullet-point list, in the form of a\n                 string. These are tuned on the career_track\n            ',
            tunable=_get_career_notification_tunable_factory(),
            tuning_group=GroupNames.UI),
        'end_of_gig_overmax_rewardless_notification':
        OptionalTunable(
            description=
            '\n            If tuned, the notification that will be used if the sim gains an\n            overmax level with no reward during this gig. Will override the\n            overmax rewardless notification in career messages.The following\n            tokens are provided:\n            * 0: The Sim owner of the career\n            * 1: The level name (e.g. Chef)\n            * 2: The career name (e.g. Culinary)\n            * 3: The company name (e.g. Maids United)\n            * 4: The overmax level\n            * 5: The pay for the gig\n            * 6: Additional pay tuned at additional_pay_per_overmax_level \n            ',
            tunable=_get_career_notification_tunable_factory(),
            tuning_group=GroupNames.UI),
        'end_of_gig_promotion_text':
        OptionalTunable(
            description=
            '\n            A string that, if enabled, will be pre-pended to the bullet\n            list of results in the promotion notification. Tokens are:\n            * 0 : The Sim owner of the career\n            ',
            tunable=TunableLocalizedStringFactory(),
            tuning_group=GroupNames.UI),
        'end_of_gig_demotion_text':
        OptionalTunable(
            description=
            '\n            A string that, if enabled, will be pre-pended to the bullet\n            list of results in the promotion notification. Tokens are:\n            * 0 : The Sim owner of the career\n            ',
            tunable=TunableLocalizedStringFactory(),
            tuning_group=GroupNames.UI),
        'odd_job_tuning':
        OptionalTunable(
            description=
            '\n            Tuning specific to odd jobs. Leave untuned if this gig is not an\n            odd job.\n            ',
            tunable=TunableTuple(
                customer_description=TunableLocalizedStringFactory(
                    description=
                    '\n                    The description of the odd job written by the customer.\n                    Token 0 is the customer sim.\n                    '
                ),
                use_customer_description_as_gig_description=Tunable(
                    description=
                    '\n                    If checked, the customer description will be used as the\n                    gig description. This description is used as the tooltip\n                    for the gig icon in the career panel.\n                    ',
                    tunable_type=bool,
                    default=False),
                result_based_gig_gratuity_multipliers=TunableMapping(
                    description=
                    '\n                    A set of multipliers for the gig gratuity.  This maps the\n                    result type of the gig and the gratuity multiplier (a \n                    percentage).  The base pay will be multiplied by this \n                    multiplier in order to determine the actual gratuity \n                    amount.\n                    ',
                    key_type=TunableEnumEntry(
                        description=
                        '\n                        The GigResult enum that represents the outcome of the \n                        Gig.\n                        ',
                        tunable_type=GigResult,
                        default=GigResult.SUCCESS),
                    value_type=TunableMultiplier.TunableFactory(
                        description=
                        '\n                        The gratuity multiplier to be calculated for this \n                        GigResult.\n                        '
                    )),
                result_based_gig_gratuity_chance_multipliers=TunableMapping(
                    description=
                    '\n                    A set of multipliers for determining the gig gratuity \n                    chance (i.e., the probability the Sim will receive gratuity \n                    in addition to the base pay).  The gratuity chance depends \n                    on the GigResult of the gig.  This maps the result type of \n                    the gig and the gratuity chance/percentage.  If this map\n                    (or a GigResult) is left untuned, then no gratuity is \n                    added.\n                    ',
                    key_type=TunableEnumEntry(
                        description=
                        '\n                        The GigResult enum that represents the outcome of the \n                        Gig.\n                        ',
                        tunable_type=GigResult,
                        default=GigResult.SUCCESS),
                    value_type=TunableMultiplier.TunableFactory(
                        description=
                        '\n                        The multiplier to be calculated for this GigResult.  \n                        This represents the percentage chance the Sim will \n                        receive gratuity.  If the Sim is to not receive \n                        gratuity, the base value should be 0 (without further\n                        tests).  If this Sim is guaranteed to receive gratuity,\n                        the base value should be 1 (without further tests).\n                        '
                    )),
                gig_gratuity_bullet_point_text=OptionalTunable(
                    description=
                    '\n                    If enabled, the gig gratuity will be a bullet point in the\n                    bullet pointed list of loots and money supplied to the end\n                    of gig notification (this is token 7 of that notification).\n                    If disabled, gratuity will be omitted from that list.\n                    Tokens:\n                    * 0: The sim owner of the career\n                    * 1: The customer\n                    * 2: the gratuity amount \n                    ',
                    tunable=TunableLocalizedStringFactory()))),
        'tip':
        OptionalTunable(
            description=
            '\n            A tip that is displayed with the gig in pickers and in the career\n            panel. Can produce something like "Required Skill: Fitness 2".\n            ',
            tunable=TunableTuple(
                tip_title=TunableLocalizedStringFactory(
                    description=
                    '\n                    The title string of the tip. Could be something like "Required\n                    Skill.\n                    '
                ),
                tip_text=TunableLocalizedStringFactory(
                    description=
                    '\n                    The text string of the tip. Could be something like "Fitness 2".\n                    '
                ),
                tip_icon=OptionalTunable(tunable=TunableIcon(
                    description=
                    '\n                        An icon to show along with the tip.\n                        '
                )))),
        'critical_failure_test':
        OptionalTunable(
            description=
            '\n            The tests for checking whether or not the Sim should receive the \n            CRITICAL_FAILURE outcome.  This will override other GigResult \n            behavior.\n            ',
            tunable=TunableTestSet(
                description=
                '\n                The tests to be performed on the Sim (and any customer).  If \n                the tests pass, the outcome will be CRITICAL_FAILURE.  \n                '
            ))
    }

    def __init__(self, owner, customer=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._owner = owner
        self._customer_id = customer.id if customer is not None else None
        self._upcoming_gig_time = None
        self._gig_result = None
        self._gig_pay = None
        self._gig_gratuity = None
        self._loot_strings = None
        self._gig_attended = False

    @classmethod
    def get_aspiration(cls):
        pass

    @classmethod
    def get_time_until_next_possible_gig(cls, starting_time):
        required_prep_time = cls.gig_prep_time()
        start_considering_prep = starting_time + required_prep_time
        (time_until, _) = cls.gig_time().time_until_next_scheduled_event(
            start_considering_prep)
        if not time_until:
            return
        return time_until + required_prep_time

    def register_aspiration_callbacks(self):
        aspiration = self.get_aspiration()
        if aspiration is None:
            return
        aspiration.register_callbacks()
        aspiration_tracker = self._owner.aspiration_tracker
        if aspiration_tracker is None:
            return
        aspiration_tracker.validate_and_return_completed_status(aspiration)
        aspiration_tracker.process_test_events_for_aspiration(aspiration)

    def notify_gig_attended(self):
        self._gig_attended = True

    def has_attended_gig(self):
        return self._gig_attended

    def notify_canceled(self):
        self._gig_result = GigResult.CANCELED
        self._send_gig_telemetry(TELEMETRY_GIG_PROGRESS_CANCEL)

    def get_career_performance(self, first_gig=False):
        if not self.result_based_career_performance:
            return 0
        if self.initial_result_based_career_performance is not None and first_gig and self._gig_result in self.initial_result_based_career_performance:
            return self.initial_result_based_career_performance[
                self._gig_result]
        performance_modifier = 1
        if self.result_based_career_performance_multiplier:
            if self._gig_result in self.result_based_career_performance_multiplier:
                resolver = self.get_resolver_for_gig()
                performance_modifier = self.result_based_career_performance_multiplier[
                    self._gig_result].get_multiplier(resolver)
        return self.result_based_career_performance.get(
            self._gig_result, 0) * performance_modifier

    def treat_work_time_as_due_date(self):
        return False

    @classmethod
    def create_picker_row(cls,
                          description=None,
                          scheduled_time=None,
                          owner=None,
                          gig_customer=None,
                          enabled=True,
                          **kwargs):
        tip = cls.tip
        description = cls.gig_picker_localization_format(
            cls.gig_pay.lower_bound, cls.gig_pay.upper_bound, scheduled_time,
            tip.tip_title(), tip.tip_text(), gig_customer)
        if not enabled and cls.disabled_tooltip is not None:
            row_tooltip = lambda *_: cls.disabled_tooltip(owner)
        elif cls.display_description is None:
            row_tooltip = None
        else:
            row_tooltip = lambda *_: cls.display_description(owner)
        if cls.odd_job_tuning is not None:
            customer_description = cls.odd_job_tuning.customer_description(
                gig_customer)
            row = OddJobPickerRow(customer_id=gig_customer.id,
                                  customer_description=customer_description,
                                  tip_title=tip.tip_title(),
                                  tip_text=tip.tip_text(),
                                  tip_icon=tip.tip_icon,
                                  name=cls.display_name(owner),
                                  icon=cls.display_icon,
                                  row_description=description,
                                  row_tooltip=row_tooltip,
                                  is_enable=enabled)
        else:
            row = ObjectPickerRow(name=cls.display_name(owner),
                                  icon=cls.display_icon,
                                  row_description=description,
                                  row_tooltip=row_tooltip,
                                  is_enable=enabled)
        return row

    def get_gig_time(self):
        return self._upcoming_gig_time

    def get_gig_customer(self):
        return self._customer_id

    def clean_up_gig(self):
        if self.gig_prep_tasks:
            self.prep_task_cleanup()

    def save_gig(self, gig_proto_buff):
        gig_proto_buff.gig_type = self.guid64
        gig_proto_buff.gig_time = self._upcoming_gig_time
        if hasattr(gig_proto_buff, 'gig_attended'):
            gig_proto_buff.gig_attended = self._gig_attended
        if self._customer_id:
            gig_proto_buff.customer_sim_id = self._customer_id

    def load_gig(self, gig_proto_buff):
        self._upcoming_gig_time = DateAndTime(gig_proto_buff.gig_time)
        if self.gig_prep_tasks:
            self.prep_time_start(self._owner,
                                 self.gig_prep_tasks,
                                 self.guid64,
                                 self.audio_on_prep_task_completion,
                                 from_load=True)
        if gig_proto_buff.HasField('customer_sim_id'):
            self._customer_id = gig_proto_buff.customer_sim_id
        if gig_proto_buff.HasField('gig_attended'):
            self._gig_attended = gig_proto_buff.gig_attended

    def set_gig_time(self, upcoming_gig_time):
        self._upcoming_gig_time = upcoming_gig_time

    def get_resolver_for_gig(self):
        if self._customer_id is not None:
            customer_sim_info = services.sim_info_manager().get(
                self._customer_id)
            if customer_sim_info is not None:
                return DoubleSimResolver(self._owner, customer_sim_info)
        return SingleSimResolver(self._owner)

    def set_up_gig(self):
        if self.gig_prep_tasks:
            self.prep_time_start(self._owner, self.gig_prep_tasks, self.guid64,
                                 self.audio_on_prep_task_completion)
        if self.loots_on_schedule:
            resolver = self.get_resolver_for_gig()
            for loot_actions in self.loots_on_schedule:
                loot_actions.apply_to_resolver(resolver)
        self._send_gig_telemetry(TELEMETRY_GIG_PROGRESS_STARTED)

    def collect_rabbit_hole_rewards(self):
        pass

    def _get_additional_loots(self):
        if self.result_based_loots is not None and self._gig_result is not None:
            loots = self.result_based_loots.get(self._gig_result)
            if loots is not None:
                return loots
        return ()

    def collect_additional_rewards(self):
        loots = self._get_additional_loots()
        if loots:
            self._loot_strings = []
            resolver = self.get_resolver_for_gig()
            for loot_actions in loots:
                self._loot_strings.extend(
                    loot_actions.apply_to_resolver_and_get_display_texts(
                        resolver))

    def _determine_gig_outcome(self):
        raise NotImplementedError

    def get_pay(self, overmax_level=None, **kwargs):
        self._determine_gig_outcome()
        pay = self.gig_pay.lower_bound
        if self.additional_pay_per_overmax_level:
            pay = pay + overmax_level * self.additional_pay_per_overmax_level
        resolver = self.get_resolver_for_gig()
        if self.result_based_gig_pay_multipliers:
            if self._gig_result in self.result_based_gig_pay_multipliers:
                multiplier = self.result_based_gig_pay_multipliers[
                    self._gig_result].get_multiplier(resolver)
                pay = int(pay * multiplier)
        gratuity = 0
        if self.odd_job_tuning:
            if self.odd_job_tuning.result_based_gig_gratuity_multipliers:
                if self.odd_job_tuning.result_based_gig_gratuity_chance_multipliers:
                    gratuity_multiplier = 0
                    gratuity_chance = 0
                    if self._gig_result in self.odd_job_tuning.result_based_gig_gratuity_chance_multipliers:
                        gratuity_chance = self.odd_job_tuning.result_based_gig_gratuity_chance_multipliers[
                            self._gig_result].get_multiplier(resolver)
                    if random.random() <= gratuity_chance:
                        if self._gig_result in self.odd_job_tuning.result_based_gig_gratuity_multipliers:
                            gratuity_multiplier = self.odd_job_tuning.result_based_gig_gratuity_multipliers[
                                self._gig_result].get_multiplier(resolver)
                            gratuity = int(pay * gratuity_multiplier)
        self._gig_pay = pay
        self._gig_gratuity = gratuity
        return pay + gratuity

    def get_promotion_evaluation_result(self,
                                        reward_text,
                                        *args,
                                        first_gig=False,
                                        **kwargs):
        if self._gig_result is not None and self.end_of_gig_notifications is not None:
            notification = self.end_of_gig_notifications.get(
                self._gig_result, None)
            if notification:
                customer_sim_info = services.sim_info_manager().get(
                    self._customer_id)
                if self.end_of_gig_promotion_text and not first_gig:
                    results_list = self.get_results_list(
                        self.end_of_gig_promotion_text(self._owner))
                else:
                    results_list = self.get_results_list()
                return EvaluationResult(Evaluation.PROMOTED, notification,
                                        self._gig_pay, self._gig_gratuity,
                                        customer_sim_info, results_list)

    def get_demotion_evaluation_result(self, *args, first_gig=False, **kwargs):
        if self._gig_result is not None and self.end_of_gig_notifications is not None:
            notification = self.end_of_gig_notifications.get(
                self._gig_result, None)
            if notification:
                customer_sim_info = services.sim_info_manager().get(
                    self._customer_id)
                if self.end_of_gig_demotion_text and not first_gig:
                    results_list = self.get_results_list(
                        self.end_of_gig_demotion_text(self._owner))
                else:
                    results_list = self.get_results_list()
                return EvaluationResult(Evaluation.DEMOTED, notification,
                                        self._gig_pay, self._gig_gratuity,
                                        customer_sim_info, results_list)

    def get_overmax_evaluation_result(self, overmax_level, reward_text, *args,
                                      **kwargs):
        if reward_text and self.end_of_gig_overmax_notification:
            return EvaluationResult(Evaluation.PROMOTED,
                                    self.end_of_gig_overmax_notification,
                                    overmax_level, self._gig_pay,
                                    self.additional_pay_per_overmax_level,
                                    reward_text)
        elif self.end_of_gig_overmax_rewardless_notification:
            return EvaluationResult(
                Evaluation.PROMOTED,
                self.end_of_gig_overmax_rewardless_notification, overmax_level,
                self._gig_pay, self.additional_pay_per_overmax_level)

    def _get_strings_for_results_list(self):
        strings = []
        if self._gig_pay is not None:
            strings.append(LocalizationHelperTuning.MONEY(self._gig_pay))
        if self.odd_job_tuning is not None and self._gig_gratuity:
            gratuity_text_factory = self.odd_job_tuning.gig_gratuity_bullet_point_text
            if gratuity_text_factory is not None:
                customer_sim_info = services.sim_info_manager().get(
                    self._customer_id)
                gratuity_text = gratuity_text_factory(self._owner,
                                                      customer_sim_info,
                                                      self._gig_gratuity)
                strings.append(gratuity_text)
        if self._loot_strings:
            strings.extend(self._loot_strings)
        return strings

    def get_results_list(self, *additional_tokens):
        return LocalizationHelperTuning.get_bulleted_list(
            None, *additional_tokens, *self._get_strings_for_results_list())

    def get_end_of_gig_evaluation_result(self, **kwargs):
        if self._gig_result is not None and self.end_of_gig_notifications is not None:
            notification = self.end_of_gig_notifications.get(
                self._gig_result, None)
            if notification:
                customer_sim_info = services.sim_info_manager().get(
                    self._customer_id)
                return EvaluationResult(Evaluation.ON_TARGET, notification,
                                        self._gig_pay, self._gig_gratuity,
                                        customer_sim_info,
                                        self.get_results_list())

    @classmethod
    def _get_base_pay_for_gig_owner(cls, owner):
        overmax_pay = cls.additional_pay_per_overmax_level
        if overmax_pay is not None:
            career = owner.career_tracker.get_career_by_uid(cls.career.guid64)
            if career is not None:
                overmax_pay *= career.overmax_level
                return (cls.gig_pay.lower_bound + overmax_pay,
                        cls.gig_pay.upper_bound + overmax_pay)
        return (cls.gig_pay.lower_bound, cls.gig_pay.upper_bound)

    @classmethod
    def build_gig_msg(cls, msg, sim, gig_time=None, gig_customer=None):
        msg.gig_type = cls.guid64
        msg.gig_name = cls.display_name(sim)
        (pay_lower, pay_upper) = cls._get_base_pay_for_gig_owner(sim)
        msg.min_pay = pay_lower
        msg.max_pay = pay_upper
        msg.gig_icon = ResourceKey()
        msg.gig_icon.instance = cls.display_icon.instance
        msg.gig_icon.group = cls.display_icon.group
        msg.gig_icon.type = cls.display_icon.type
        if cls.odd_job_tuning is not None and cls.odd_job_tuning.use_customer_description_as_gig_description and gig_customer is not None:
            customer_sim_info = services.sim_info_manager().get(gig_customer)
            if customer_sim_info is not None:
                msg.gig_description = cls.odd_job_tuning.customer_description(
                    customer_sim_info)
        else:
            msg.gig_description = cls.display_description(sim)
        if gig_time is not None:
            msg.gig_time = gig_time
        if gig_customer is not None:
            msg.customer_id = gig_customer
        if cls.tip is not None:
            msg.tip_title = cls.tip.tip_title()
            if cls.tip.tip_icon is not None or cls.tip.tip_text is not None:
                build_icon_info_msg(
                    IconInfoData(icon_resource=cls.tip.tip_icon),
                    None,
                    msg.tip_icon,
                    desc=cls.tip.tip_text())

    def send_prep_task_update(self):
        if self.gig_prep_tasks:
            self._prep_task_tracker.send_prep_task_update()

    def _apply_payout_stat(self, medal, payout_display_data=None):
        owner_sim = self._owner
        resolver = SingleSimResolver(owner_sim)
        payout_stats = self.payout_stat_data
        for stat in payout_stats.keys():
            stat_tracker = owner_sim.get_tracker(stat)
            if not owner_sim.get_tracker(stat).has_statistic(stat):
                continue
            stat_data = payout_stats[stat]
            stat_multiplier = 1.0
            if medal in stat_data.medal_to_payout:
                multiplier = stat_data.medal_to_payout[medal]
                stat_multiplier = multiplier.get_multiplier(resolver)
            stat_total = stat_data.base_amount * stat_multiplier
            stat_tracker.add_value(stat, stat_total)
            if payout_display_data is not None:
                for threshold_data in stat_data.ui_threshold:
                    if stat_total >= threshold_data.threshold:
                        payout_display_data.append(threshold_data)
                        break

    def _send_gig_telemetry(self, progress):
        with telemetry_helper.begin_hook(gig_telemetry_writer,
                                         TELEMETRY_HOOK_GIG_PROGRESS,
                                         sim_info=self._owner) as hook:
            hook.write_int(TELEMETRY_CAREER_ID, self.career.guid64)
            hook.write_int(TELEMETRY_GIG_ID, self.guid64)
            hook.write_int(TELEMETRY_GIG_PROGRESS_NUMBER, progress)
Example #7
0
class Skill(HasTunableReference, ProgressiveStatisticCallbackMixin, statistics.continuous_statistic_tuning.TunedContinuousStatistic, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)):
    SKILL_LEVEL_LIST = TunableMapping(description='\n        A mapping defining the level boundaries for each skill type.\n        ', key_type=SkillLevelType, value_type=TunableList(description='\n            The level boundaries for skill type, specified as a delta from the\n            previous value.\n            ', tunable=Tunable(tunable_type=int, default=0)), tuple_name='SkillLevelListMappingTuple', export_modes=ExportModes.All)
    SKILL_EFFECTIVENESS_GAIN = TunableMapping(description='\n        Skill gain points based on skill effectiveness.\n        ', key_type=SkillEffectiveness, value_type=TunableCurve())
    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            The name of this skill.\n            ', export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'skill_description': TunableLocalizedString(description="\n            The skill's normal description.\n            ", export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'locked_description': TunableLocalizedString(description="\n            The skill description when it's locked.\n            ", allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'icon': TunableIcon(description='\n            Icon to be displayed for the Skill.\n            ', export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'tooltip_icon_list': TunableList(description='\n            A list of icons to show in the tooltip of this\n            skill.\n            ', tunable=TunableIcon(description='\n                Icon that is displayed what types of objects help\n                improve this skill.\n                '), export_modes=(ExportModes.ClientBinary,), tuning_group=GroupNames.UI), '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), allow_none=True, class_restrictions=('Tutorial',), tuning_group=GroupNames.UI), 'priority': Tunable(description="\n            Skill priority.  Higher priority skills will trump other skills when\n            being displayed on the UI. When a Sim gains multiple skills at the\n            same time, only the highest priority one will display a progress bar\n            over the Sim's head.\n            ", tunable_type=int, default=1, export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'next_level_teaser': TunableList(description='\n            Tooltip which describes what the next level entails.\n            ', tunable=TunableLocalizedString(), export_modes=(ExportModes.ClientBinary,), 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(), allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'stat_asm_param': TunableStatAsmParam.TunableFactory(tuning_group=GroupNames.ANIMATION), 'hidden': Tunable(description='\n            If checked, this skill will be hidden.\n            ', tunable_type=bool, default=False, export_modes=ExportModes.All, tuning_group=GroupNames.AVAILABILITY), 'update_client_for_npcs': Tunable(description="\n            Whether this skill will send update messages to the client\n            for non-active household sims (NPCs).\n            \n            e.g. A toddler's communication skill determines the VOX they use, so\n            the client needs to know the skill level for all toddlers in order\n            for this work properly.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'is_default': Tunable(description='\n            Whether Sim will default has this skill.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'ages': TunableSet(description='\n            Allowed ages for this skill.\n            ', tunable=TunableEnumEntry(tunable_type=Age, default=Age.ADULT, export_modes=ExportModes.All), tuning_group=GroupNames.AVAILABILITY), '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)), tuning_group=GroupNames.AUTONOMY), 'weight': Tunable(description="\n            The weight of the Skill with regards to autonomy.  It's ignored for\n            the purposes of sorting stats, but it's applied when scoring the\n            actual statistic operation for the SI.\n            ", tunable_type=float, default=0.5, tuning_group=GroupNames.AUTONOMY), '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), 'tags': TunableList(description='\n            The associated categories of the skill\n            ', tunable=TunableEnumEntry(tunable_type=tag.Tag, default=tag.Tag.INVALID, pack_safe=True), tuning_group=GroupNames.CORE), 'skill_level_type': TunableEnumEntry(description='\n            Skill level list to use.\n            ', tunable_type=SkillLevelType, default=SkillLevelType.MAJOR, export_modes=ExportModes.All, tuning_group=GroupNames.CORE), '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()), skill_level_buff=OptionalTunable(tunable=TunableReference(description='\n                        The buff to place on a Sim when they reach this specific\n                        level of skill.\n                        ', manager=services.buff_manager())), rewards=TunableList(description='\n                    A reward to give for achieving this level.\n                    ', tunable=rewards.reward_tuning.TunableSpecificReward(pack_safe=True)), loot=TunableList(description='\n                    A loot to apply for achieving this level.\n                    ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',))), super_affordances=TunableSet(description='\n                    Super affordances this adds to the Sim.\n                    ', tunable=TunableReference(description='\n                        A super affordance added to this Sim.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), class_restrictions=('SuperInteraction',), pack_safe=True)), target_super_affordances=TunableProvidedAffordances(description='\n                    Super affordances this adds to the target.\n                    ', locked_args={'target': ParticipantType.Object, 'carry_target': ParticipantType.Invalid, 'is_linked': False, 'unlink_if_running': False}), actor_mixers=TunableMapping(description='\n                    Mixers this adds to an associated actor object. (When targeting\n                    something else.)\n                    ', key_type=TunableReference(description='\n                        The super affordance these mixers are associated with.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), class_restrictions=('SuperInteraction',), pack_safe=True), value_type=TunableSet(description='\n                        Set of mixer affordances associated with the super affordance.\n                        ', tunable=TunableReference(description='\n                            Linked mixer affordance.\n                            ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), category='asm', class_restrictions=('MixerInteraction',), pack_safe=True)))), tuning_group=GroupNames.CORE), 'age_up_skill_transition_data': OptionalTunable(description='\n            Data used to modify the value of a new skill based on the level\n            of this skill.\n            \n            e.g. Toddler Communication skill transfers into Child Social skill.\n            ', tunable=TunableTuple(new_skill=TunablePackSafeReference(description='\n                    The new skill.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)), skill_data=TunableMapping(description="\n                    A mapping between this skill's levels and the\n                    new skill's internal value.\n                    \n                    The keys are user facing skill levels.\n                    \n                    The values are the internal statistic value, not the user\n                    facing skill level.\n                    ", key_type=Tunable(description="\n                        This skill's level.\n                        \n                        This is the actual user facing skill level.\n                        ", tunable_type=int, default=0), value_type=Tunable(description='\n                        The new skill\'s value.\n                        \n                        This is the internal statistic\n                        value, not the user facing skill level."\n                        ', tunable_type=int, default=0))), tuning_group=GroupNames.SPECIAL_CASES), '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',), pack_safe=True), tuning_group=GroupNames.SPECIAL_CASES), 'trend_tag': OptionalTunable(description='\n            If enabled, we associate this skill with a particular trend via tag\n            which you can find in trend_tuning.\n            ', tunable=TunableTag(description='\n                The trend tag we associate with this skill\n                ', filter_prefixes=('func_trend',)))}
    REMOVE_INSTANCE_TUNABLES = ('min_value_tuning', 'max_value_tuning', 'decay_rate', '_default_convergence_value')

    def __init__(self, tracker):
        self._skill_level_buff = None
        super().__init__(tracker, self.initial_value)
        self._delta_enabled = True
        self._max_level_update_sent = False

    @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')

    @classproperty
    def skill_type(cls):
        return cls

    @constproperty
    def is_skill():
        return True

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

    @constproperty
    def remove_on_convergence():
        return False

    @classproperty
    def valid_for_stat_testing(cls):
        return True

    @classmethod
    def can_add(cls, owner, force_add=False, **kwargs):
        if force_add:
            return True
        if owner.age not in cls.ages:
            return False
        return super().can_add(owner, **kwargs)

    @classmethod
    def convert_to_user_value(cls, value):
        level_list = cls.get_level_list()
        if not level_list:
            return 0
        current_value = value
        for (level, level_threshold) in enumerate(level_list):
            current_value -= level_threshold
            if 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 create_skill_update_msg(cls, sim_id, stat_value):
        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

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

    @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

    def _get_level_data_for_skill_level(self, skill_level):
        level_data = self.level_data.get(skill_level)
        if level_data is None:
            logger.debug('No level data found for skill [{}] at level [{}].', self, skill_level)
        return level_data

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

    def should_send_update(self, sim_info, stat_value):
        if sim_info.is_npc and not self.update_client_for_npcs:
            return False
        if self.hidden:
            return False
        if Skill.convert_to_user_value(stat_value) == 0:
            return False
        if self.reached_max_level:
            if self._max_level_update_sent:
                return False
            self._max_level_update_sent = True
        return True

    def on_initial_startup(self):
        super().on_initial_startup()
        skill_level = self.get_user_value()
        self._update_skill_level_buff(skill_level)

    def on_add(self):
        super().on_add()
        self._tracker.owner.add_modifiers_for_skill(self)
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is not None:
            provided_affordances = []
            for provided_affordance in level_data.target_super_affordances:
                provided_affordance_data = ProvidedAffordanceData(provided_affordance.affordance, provided_affordance.object_filter, provided_affordance.allow_self)
                provided_affordances.append(provided_affordance_data)
            self._tracker.add_to_affordance_caches(level_data.super_affordances, provided_affordances)
            self._tracker.add_to_actor_mixer_cache(level_data.actor_mixers)
            sim = self._tracker._owner.get_sim_instance()
            apply_super_affordance_commodity_flags(sim, self, level_data.super_affordances)

    def on_remove(self, on_destroy=False):
        super().on_remove(on_destroy=on_destroy)
        self._destory_callback_handle()
        if not on_destroy:
            self._send_skill_delete_message()
        if self._skill_level_buff is not None:
            self._tracker.owner.remove_buff(self._skill_level_buff)
            self._skill_level_buff = None
        if not on_destroy:
            self._tracker.update_affordance_caches()
        sim = self._tracker._owner.get_sim_instance()
        remove_super_affordance_commodity_flags(sim, self)

    def on_zone_load(self):
        self._max_level_update_sent = False

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

    @classproperty
    def default_value(cls):
        return cls.initial_value

    @flexmethod
    @caches.cached
    def get_user_value(cls, inst):
        inst_or_cls = inst if inst is not None else cls
        return super(__class__, inst_or_cls).get_user_value()

    def _clear_user_value_cache(self):
        self.get_user_value.func.cache.clear()

    def set_value(self, value, *args, from_load=False, interaction=None, **kwargs):
        old_value = self.get_value()
        super().set_value(value, *args, **kwargs)
        if not caches.skip_cache:
            self._clear_user_value_cache()
        if from_load:
            return
        event_manager = services.get_event_manager()
        sim_info = self._tracker._owner
        new_value = self.get_value()
        new_level = self.convert_to_user_value(value)
        if old_value == self.initial_value or old_value != new_value:
            event_manager.process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, skill=self, statistic=self.stat_type, custom_keys=(self.stat_type,))
        old_level = self.convert_to_user_value(old_value)
        if old_level < new_level or old_value == self.initial_value:
            self._apply_multipliers_to_continuous_statistics()
            event_manager.process_event(test_events.TestEvent.SkillLevelChange, sim_info=sim_info, skill=self, new_level=new_level, custom_keys=(self.stat_type,))

    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)
        if not caches.skip_cache:
            self._clear_user_value_cache()
        if interaction is not None:
            interaction_name = interaction.affordance.__name__
        else:
            interaction_name = TELEMETRY_INTERACTION_NOT_AVAILABLE
        self.on_skill_updated(telemhook, old_value, self.get_value(), interaction_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()
        if not caches.skip_cache:
            self._clear_user_value_cache()
        new_value = self._value
        if old_value < new_value:
            event_manager = services.get_event_manager()
            sim_info = self._tracker._owner if self._tracker is not None else None
            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)
            event_manager.process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, skill=self, statistic=self.stat_type, custom_keys=(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_level < new_level or old_value == self.initial_value:
                if self._tracker is not None:
                    self._tracker.notify_watchers(self.stat_type, self._value, self._value)
                event_manager.process_event(test_events.TestEvent.SkillLevelChange, sim_info=sim_info, skill=self, new_level=new_level, custom_keys=(self.stat_type,))

    def _on_statistic_modifier_changed(self, notify_watcher=True):
        super()._on_statistic_modifier_changed(notify_watcher=notify_watcher)
        if not self.reached_max_level:
            return
        event_manager = services.get_event_manager()
        sim_info = self._tracker._owner if self._tracker is not None else None
        event_manager.process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, skill=self, statistic=self.stat_type, custom_keys=(self.stat_type,))

    def on_skill_updated(self, telemhook, old_value, new_value, affordance_name):
        owner_sim_info = self._tracker._owner
        if owner_sim_info.is_selectable:
            with telemetry_helper.begin_hook(skill_telemetry_writer, telemhook, sim_info=owner_sim_info) 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._handle_skill_up(skill_level)

    def _send_skill_delete_message(self):
        if self.tracker.owner.is_npc:
            return
        skill_msg = Commodities_pb2.SkillDelete()
        skill_msg.skill_id = self.guid64
        op = GenericProtocolBufferOp(Operation.SIM_SKILL_DELETE, skill_msg)
        Distributor.instance().add_op(self.tracker.owner, op)

    @staticmethod
    def _callback_handler(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_threshold_callback()

    def _handle_skill_up(self, skill_level):
        self._show_level_notification(skill_level)
        self._update_skill_level_buff(skill_level)
        self._try_give_skill_up_payout(skill_level)
        self._tracker.update_affordance_caches()
        sim = self._tracker._owner.get_sim_instance()
        remove_super_affordance_commodity_flags(sim, self)
        super_affordances = tuple(self._tracker.get_cached_super_affordances_gen())
        apply_super_affordance_commodity_flags(sim, self, super_affordances)

    def _recalculate_modified_decay_rate(self):
        pass

    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.create_and_add_callback_listener(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_info=sim_info) as hook:
            hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64)
            hook.write_int(TELEMETRY_FIELD_SKILL_LEVEL, new_level)
        self._handle_skill_up(new_level)
        services.get_event_manager().process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, statistic=self.stat_type, custom_keys=(self.stat_type,))

    def _show_level_notification(self, skill_level, ignore_npc_check=False):
        sim_info = self._tracker._owner
        if not (ignore_npc_check or not sim_info.is_npc):
            if skill_level == 1:
                tutorial_service = services.get_tutorial_service()
                if tutorial_service is not None and tutorial_service.is_tutorial_running():
                    return
            level_data = self._get_level_data_for_skill_level(skill_level)
            if level_data is not None:
                tutorial_id = None
                if self.tutorial is not None:
                    if 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=IconInfoData(icon_resource=self.icon), secondary_icon_override=IconInfoData(obj_instance=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)

    def _update_skill_level_buff(self, skill_level):
        level_data = self._get_level_data_for_skill_level(skill_level)
        new_buff = level_data.skill_level_buff if level_data is not None else None
        if self._skill_level_buff is not None:
            self._tracker.owner.remove_buff(self._skill_level_buff)
            self._skill_level_buff = None
        if new_buff is not None:
            self._skill_level_buff = self._tracker.owner.add_buff(new_buff)

    def _try_give_skill_up_payout(self, skill_level):
        level_data = self._get_level_data_for_skill_level(skill_level)
        if level_data is None:
            return
        if level_data.rewards:
            for reward in level_data.rewards:
                reward().open_reward(self._tracker.owner, reward_destination=RewardDestination.SIM, reward_source=self)
        if level_data.loot:
            resolver = SingleSimResolver(self._tracker.owner)
            for loot in level_data.loot:
                loot.apply_to_resolver(resolver)

    def force_show_level_notification(self, skill_level):
        self._show_level_notification(skill_level, ignore_npc_check=True)

    @classmethod
    def send_commodity_update_message(cls, sim_info, old_value, new_value):
        stat_instance = sim_info.get_statistic(cls.stat_type, add=False)
        if stat_instance is None or not stat_instance.should_send_update(sim_info, new_value):
            return
        msg = cls.create_skill_update_msg(sim_info.id, new_value)
        add_object_message(sim_info, MSG_SIM_SKILL_UPDATE, msg, False)
        change_rate = stat_instance.get_change_rate()
        hide_progress_bar = False
        if sim_info.is_npc or sim_info.is_skill_bar_suppressed():
            hide_progress_bar = True
        op = distributor.ops.SkillProgressUpdate(cls.guid64, change_rate, new_value, hide_progress_bar)
        distributor.ops.record(sim_info, op)

    def save_statistic(self, commodities, skills, ranked_stats, tracker):
        current_value = self.get_saved_value()
        if current_value == self.initial_value:
            return
        message = protocols.Skill()
        message.name_hash = self.guid64
        message.value = current_value
        if self._time_of_last_value_change:
            message.time_of_last_value_change = self._time_of_last_value_change.absolute_ticks()
        skills.append(message)

    def unlocks_skills_on_max(self):
        return True

    def can_decay(self):
        return False

    def get_skill_provided_affordances(self):
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is None:
            return ((), ())
        return (level_data.super_affordances, level_data.target_super_affordances)

    def get_skill_provided_actor_mixers(self):
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is None:
            return
        return level_data.actor_mixers

    def get_actor_mixers(self, super_interaction):
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is None:
            return []
        mixers = level_data.actor_mixers.get(super_interaction, tuple()) if level_data is not None else []
        return mixers

    @flexmethod
    def populate_localization_token(cls, inst, token):
        inst_or_cls = inst if inst is not None else cls
        token.type = LocalizedStringToken.STRING
        token.text_string = inst_or_cls.stat_name
class AuditionDramaNode(BaseDramaNode):
    INSTANCE_TUNABLES = {
        'gig':
        Gig.TunableReference(
            description='\n            Gig this audition is for.\n            '
        ),
        'audition_prep_time':
        TunableTimeSpan(
            description=
            '\n            Amount of time between the seed of the potential audition node\n            to the start of the audition time. \n            ',
            default_hours=5),
        'audition_prep_recommendation':
        TunableLocalizedStringFactory(
            description=
            '\n            String that gives the player more information on how to succeed\n            in this audition.\n            '
        ),
        'audition_prep_icon':
        OptionalTunable(
            description=
            '\n            If enabled, this icon will be displayed with the audition preparation.\n            ',
            tunable=TunableIcon(
                description=
                '\n                Icon for audition preparation.\n                '
            )),
        'audition_outcomes':
        TunableList(
            description=
            '\n            List of loot and multipliers which are for audition outcomes.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The information needed to determine whether or not the sim passes\n                or fails this audition. We cannot rely on the outcome of the \n                interaction because we need to run this test on uninstantiated \n                sims as well. This is similar to the fallback outcomes in \n                interactions.\n                ',
                loot_list=TunableList(
                    description=
                    '\n                    Loot applied if this outcome is chosen\n                    ',
                    tunable=LootActions.TunableReference(pack_safe=True)),
                weight=TunableMultiplier.TunableFactory(
                    description=
                    '\n                    A tunable list of tests and multipliers to apply to the \n                    weight of the outcome.\n                    '
                ),
                is_success=Tunable(
                    description=
                    '\n                    Whether or not this is considered a success outcome.\n                    ',
                    tunable_type=bool,
                    default=False))),
        'audition_rabbit_hole':
        RabbitHole.TunableReference(
            description=
            '\n            Data required to put sim in rabbit hole.\n            '
        ),
        'skip_audition':
        OptionalTunable(
            description=
            '\n            If enabled, we can skip auditions if sim passes tuned tests.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Data related to whether or not this audition can be skipped.\n                ',
                skip_audition_tests=TunableTestSet(
                    description=
                    '\n                    Test to see if sim can skip this audition.\n                    '
                ),
                skipped_audition_loot=TunableList(
                    description=
                    '\n                    Loot applied if sim manages to skip audition\n                    ',
                    tunable=LootActions.TunableReference(pack_safe=True)))),
        'advance_notice_time':
        TunableTimeSpan(
            description=
            '\n            The amount of time between the alert and the start of the event.\n            ',
            default_hours=1,
            locked_args={
                'days': 0,
                'minutes': 0
            }),
        'loot_on_schedule':
        TunableList(
            description=
            '\n            Loot applied if the audition drama node is scheduled successfully.\n            ',
            tunable=LootActions.TunableReference(pack_safe=True)),
        'advance_notice_notification':
        TunableUiDialogNotificationSnippet(
            description=
            '\n            The notification that is displayed at the advance notice time.\n            '
        )
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._calculated_audition_time = None
        self._calculated_gig_time = None
        self._rabbit_hole_id = None

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

    @property
    def _require_instanced_sim(self):
        return False

    @classproperty
    def persist_when_active(cls):
        return True

    def get_picker_schedule_time(self):
        return self._calculated_audition_time

    def create_picker_row(self, owner=None, **kwargs):
        now_time = services.game_clock_service().now()
        min_audition_time = now_time + self.audition_prep_time()
        possible_audition_times = self.get_final_times_based_on_schedule(
            self.min_and_max_times,
            anchor_time=min_audition_time,
            scheduled_time_only=True)
        audition_time = min_audition_time
        if possible_audition_times is not None:
            now = services.time_service().sim_now
            for possible_audition_time in possible_audition_times:
                if possible_audition_time[0] >= now:
                    audition_time = possible_audition_time[0]
                    break
        gig = self.gig
        time_till_gig = gig.get_time_until_next_possible_gig(audition_time)
        if time_till_gig is None:
            return
        gig_time = audition_time + time_till_gig
        if self.skip_audition and self.skip_audition.skip_audition_tests.run_tests(
                SingleSimResolver(owner)):
            formatted_string = Career.GIG_PICKER_SKIPPED_AUDITION_LOCALIZATION_FORMAT(
                gig.gig_pay.lower_bound, gig.gig_pay.upper_bound, gig_time,
                self.audition_prep_recommendation())
        else:
            formatted_string = Career.GIG_PICKER_LOCALIZATION_FORMAT(
                gig.gig_pay.lower_bound, gig.gig_pay.upper_bound,
                audition_time, gig_time, self.audition_prep_recommendation())
        self._calculated_audition_time = audition_time
        self._calculated_gig_time = gig_time
        return gig.create_picker_row(formatted_string, owner)

    def schedule(self,
                 resolver,
                 specific_time=None,
                 time_modifier=TimeSpan.ZERO):
        if self.skip_audition and self.skip_audition.skip_audition_tests.run_tests(
                resolver):
            for loot in self.skip_audition.skipped_audition_loot:
                loot.apply_to_resolver(resolver)
            resolver.sim_info_to_test.career_tracker.set_gig(
                self.gig, self._calculated_gig_time)
            return False
        success = super().schedule(resolver,
                                   specific_time=specific_time,
                                   time_modifier=time_modifier)
        if success:
            services.calendar_service().mark_on_calendar(
                self, advance_notice_time=self.advance_notice_time())
            self._send_career_ui_update(is_add=True)
            for loot in self.loot_on_schedule:
                loot.apply_to_resolver(resolver)
        return success

    def cleanup(self, from_service_stop=False):
        services.calendar_service().remove_on_calendar(self.uid)
        self._send_career_ui_update(is_add=False)
        rabbit_hole_service = services.get_rabbit_hole_service()
        if self._rabbit_hole_id and rabbit_hole_service.is_in_rabbit_hole(
                self._receiver_sim_info.id,
                rabbit_hole_id=self._rabbit_hole_id):
            rabbit_hole_service.remove_rabbit_hole_expiration_callback(
                self._receiver_sim_info.id, self._rabbit_hole_id,
                self._on_sim_return)
        super().cleanup(from_service_stop=from_service_stop)

    def resume(self):
        if self._rabbit_hole_id and not services.get_rabbit_hole_service(
        ).is_in_rabbit_hole(self._receiver_sim_info.id,
                            rabbit_hole_id=self._rabbit_hole_id):
            services.drama_scheduler_service().complete_node(self.uid)

    def _run(self):
        rabbit_hole_service = services.get_rabbit_hole_service()
        self._rabbit_hole_id = rabbit_hole_service.put_sim_in_managed_rabbithole(
            self._receiver_sim_info, self.audition_rabbit_hole)
        if self._rabbit_hole_id is None:
            self._on_sim_return(canceled=True)
        rabbit_hole_service.set_rabbit_hole_expiration_callback(
            self._receiver_sim_info.id, self._rabbit_hole_id,
            self._on_sim_return)
        return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE

    def _on_sim_return(self, canceled=False):
        receiver_sim_info = self._receiver_sim_info
        resolver = SingleSimResolver(receiver_sim_info)
        weights = []
        failure_outcomes = []
        for outcome in self.audition_outcomes:
            if canceled:
                if not outcome.is_success:
                    failure_outcomes.append(outcome)
                    weight = outcome.weight.get_multiplier(resolver)
                    if weight > 0:
                        weights.append((weight, outcome))
            else:
                weight = outcome.weight.get_multiplier(resolver)
                if weight > 0:
                    weights.append((weight, outcome))
        if failure_outcomes:
            selected_outcome = random.choice(failure_outcomes)
        else:
            selected_outcome = sims4.random.weighted_random_item(weights)
        if not selected_outcome:
            logger.error(
                'No valid outcome is tuned on this audition. Verify weights in audition_outcome for {}.',
                self.guid64)
            services.drama_scheduler_service().complete_node(self.uid)
            return
        if selected_outcome.is_success:
            receiver_sim_info.career_tracker.set_gig(self.gig,
                                                     self._calculated_gig_time)
        for loot in selected_outcome.loot_list:
            loot.apply_to_resolver(resolver)
        services.drama_scheduler_service().complete_node(self.uid)

    def _save_custom_data(self, writer):
        if self._calculated_audition_time is not None:
            writer.write_uint64(AUDITION_TIME_TOKEN,
                                self._calculated_audition_time)
        if self._calculated_gig_time is not None:
            writer.write_uint64(GIG_TIME_TOKEN, self._calculated_gig_time)
        if self._rabbit_hole_id is not None:
            writer.write_uint64(RABBIT_HOLE_ID_TOKEN, self._rabbit_hole_id)

    def _load_custom_data(self, reader):
        self._calculated_audition_time = DateAndTime(
            reader.read_uint64(AUDITION_TIME_TOKEN, None))
        self._calculated_gig_time = DateAndTime(
            reader.read_uint64(GIG_TIME_TOKEN, None))
        self._rabbit_hole_id = reader.read_uint64(RABBIT_HOLE_ID_TOKEN, None)
        rabbit_hole_service = services.get_rabbit_hole_service()
        if not self._rabbit_hole_id:
            rabbit_hole_service = services.get_rabbit_hole_service()
            self._rabbit_hole_id = services.get_rabbit_hole_service(
            ).get_rabbit_hole_id_by_type(self._receiver_sim_info.id,
                                         self.audition_rabbit_hole)
        if self._rabbit_hole_id and rabbit_hole_service.is_in_rabbit_hole(
                self._receiver_sim_info.id,
                rabbit_hole_id=self._rabbit_hole_id):
            rabbit_hole_service.set_rabbit_hole_expiration_callback(
                self._receiver_sim_info.id, self._rabbit_hole_id,
                self._on_sim_return)
        self._send_career_ui_update()
        return True

    def _send_career_ui_update(self, is_add=True):
        audition_update_msg = DistributorOps_pb2.AuditionUpdate()
        if is_add:
            self.gig.build_gig_msg(
                audition_update_msg.audition_info,
                self._receiver_sim_info,
                gig_time=self._calculated_gig_time,
                audition_time=self._calculated_audition_time)
        op = GenericProtocolBufferOp(Operation.AUDITION_UPDATE,
                                     audition_update_msg)
        build_icon_info_msg(
            IconInfoData(icon_resource=self.audition_prep_icon),
            self.audition_prep_recommendation(),
            audition_update_msg.recommended_task)
        Distributor.instance().add_op(self._receiver_sim_info, op)

    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
        services.calendar_service().mark_on_calendar(
            self, advance_notice_time=self.advance_notice_time())
        return True

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

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

    @classproperty
    def persist_when_active(cls):
        return True

    @classproperty
    def simless(cls):
        return True

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @classmethod
    def travel_to_festival(cls):
        active_sim_info = services.active_sim_info()
        active_sim = active_sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS_EXCEPT_UNINITIALIZED)
        if active_sim is None:
            return
        lot_id = cls.get_travel_lot_id(reject_same_street=cls.reject_same_street_travel)
        if lot_id is None:
            return
        pick = PickInfo(pick_type=PickType.PICK_TERRAIN, lot_id=lot_id, ignore_neighborhood_id=True)
        context = interactions.context.InteractionContext(active_sim, interactions.context.InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT, interactions.priority.Priority.High, insert_strategy=interactions.context.QueueInsertStrategy.NEXT, pick=pick)
        active_sim.push_super_affordance(FestivalDramaNode.GO_TO_FESTIVAL_INTERACTION, None, context)
Example #10
0
class NotebookEntry(HasTunableReference,
                    metaclass=HashedTunedInstanceMetaclass,
                    manager=services.get_instance_manager(
                        sims4.resources.Types.NOTEBOOK_ENTRY)):
    INSTANCE_TUNABLES = {
        'category_id':
        TunableEnumEntry(
            description=
            '\n            Category type which will define the format the UI will use\n            to display the information.\n            ',
            tunable_type=NotebookCategories,
            default=NotebookCategories.INVALID),
        'subcategory_id':
        TunableEnumEntry(
            description=
            '\n            Subcategory type which will define the format the UI will use\n            to display the information.\n            ',
            tunable_type=NotebookSubCategories,
            default=NotebookSubCategories.INVALID),
        'entry_text':
        TunableLocalizedString(
            description=
            '\n            Text to be displayed on the notebook entry.        \n            '
        ),
        'entry_icon':
        OptionalTunable(
            TunableIcon(
                description=
                '\n            Optional icon to be displayed with the entry text.\n            '
            )),
        'entry_tooltip':
        OptionalTunable(
            TunableTuple(
                description=
                '\n            Text to be displayed when the player hovers this entry.\n            ',
                tooltip_style=TunableEnumEntry(
                    description=
                    '\n                Types of possible tooltips that can be displayed for an entry. \n                ',
                    tunable_type=HovertipStyle,
                    default=HovertipStyle.HOVER_TIP_DEFAULT),
                tooltip_fields=TunableMapping(
                    description=
                    '\n                Mapping of tooltip fields to its localized values. Since \n                this fields are created from a system originally created \n                for recipes, all of them may be tuned, but these are the \n                most common fields to show on a tooltip:\n                - recipe_name = This is the actual title of the tooltip.  \n                This is the main text\n                - recipe_description = This description refers to the main \n                text that will show below the title\n                - header = Smaller text that will show just above the title\n                - subtext = Smaller text that will show just bellow the \n                title\n                ',
                    key_type=TunableEnumEntry(
                        description=
                        '\n                    Fields to be populated in the tooltip.  These fields\n                    will be populated with the text and tokens tuned.\n                    ',
                        tunable_type=TooltipFields,
                        default=TooltipFields.recipe_name),
                    value_type=TunableLocalizedString()))),
        'entry_sublist':
        OptionalTunable(
            TunableList(
                description=
                '\n            List of objects linked to a notebook entry.\n            i.e. Ingredient objects attached to a serum or to a recipe.\n            ',
                tunable=TunableTuple(
                    description=
                    '\n                Pair of object definitions and amount of objects needed\n                to \n                ',
                    object_definition=TunableReference(
                        services.definition_manager(),
                        description='Reference to ingredient object.'),
                    num_objects_required=Tunable(
                        description=
                        '\n                    Number of objects required on this field.  This will be\n                    displayed next to the current value of objects found in the \n                    inventory.\n                    Example: Serums will displayed \n                             <current_objects_held / num_objects_required>\n                    ',
                        tunable_type=int,
                        default=0)))),
        'entry_sublist_is_sortable':
        OptionalTunable(
            description=
            '\n            If enabled, entry sublist will be presented sorted alphabetically.\n            ',
            tunable=TunableTuple(include_new_entry=Tunable(
                description=
                '\n                    If checked, the new entry in entry sublist will be sorted.\n                    ',
                tunable_type=bool,
                default=False)))
    }

    def __init__(self,
                 entry_object_definition_id=None,
                 sub_entries=None,
                 new_entry=True):
        self.new_entry = new_entry
        self.entry_object_definition_id = entry_object_definition_id
        if sub_entries is not None:
            self.sub_entries = list(sub_entries)
        else:
            self.sub_entries = list()

    def has_identical_entries(self, entries):
        for entry in entries:
            if self.__class__ == entry.__class__:
                return True
        return False

    def is_definition_based(self):
        return False

    @property
    def entry_icon_info_data(self):
        if self.entry_icon is not None:
            return IconInfoData(icon_resource=self.entry_icon)
Example #11
0
class UiTuning:
    LOADING_SCREEN_STRINGS = TunableMapping(
        description=
        '\n        Mapping from the Pack to its associated loading strings.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack containing the strings.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_type=TunableList(
            description=
            '\n            The list of loading screen strings which belongs to the pack.\n            We always display the strings from base game AND from the latest\n            pack which the player is entitled to and has installed. \n            ',
            tunable=TunableLocalizedString()),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='LoadingScreenStringsTuple')
    GO_HOME_INTERACTION = TunableReference(
        description=
        '\n        The interaction to push a Sim to go home.\n        ',
        manager=services.affordance_manager(),
        export_modes=(ExportModes.ClientBinary, ))
    COME_NEAR_ACTIVE_SIM = TunableReference(
        description=
        '\n        An affordance to push on a Sim so they come near the active Sim.\n        ',
        manager=services.affordance_manager())
    BRING_HERE_INTERACTION = TunableReference(
        description=
        '\n        An affordance to push on household members to summon them to the\n        current lot if they are not instanced.\n        ',
        manager=services.affordance_manager())
    NEW_CONTENT_ALERT_TUNING = TunableMapping(
        description=
        '\n        Mapping from Pack to its associated new content alert tuning\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack containing the new content tuning. NOTE: this should never\n            be tuned to BASE_GAME. That would trigger for all users.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_type=TunableTuple(
            description=
            '\n            Each pack will have a set of tuning of images and text to display\n            to inform the user what new features have been introduced in the \n            pack.\n            ',
            export_class_name='TunablePackContentTuple',
            title=TunableLocalizedString(
                description=
                '\n                The title to be displayed at the top of the New Content Alert\n                UI for this pack.\n                '
            ),
            cycle_images=TunableList(
                description=
                '\n                A list of images (screenshots) that the UI cycles through to\n                show off some of the new features.\n                ',
                tunable=TunableResourceKey(
                    resource_types=sims4.resources.CompoundTypes.IMAGE)),
            feature_list=TunableList(
                description=
                '\n                A list of tuples that describe each new feature in the New\n                Content Alert UI. NOTE: This should NEVER have more than 4\n                elements in it.\n                ',
                maxlength=4,
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains title text, description, an icon,\n                    and a reference to the matching lesson for this new \n                    feature.\n                    ',
                    export_class_name='TunableFeatureTuple',
                    title_text=TunableLocalizedString(
                        description=
                        '\n                        A title to be displayed in bold for the feature.\n                        '
                    ),
                    description_text=TunableLocalizedString(
                        description=
                        '\n                        A short description of the new feature.\n                        '
                    ),
                    icon=TunableResourceKey(
                        description=
                        '\n                        An icon that represents the feature.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    lesson=TunableReference(
                        description=
                        '\n                        A reference to the lesson that the user can go look at\n                        for this new feature.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.TUTORIAL),
                        allow_none=True,
                        pack_safe=True)))),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='NewContentAlertTuple')
    PACK_SPECIFIC_DATA = TunableMapping(
        description=
        '\n        Mapping from a Pack to its associated data.  This includes pack icons,\n        filter strings, and the credits file.\n        ',
        key_name='packId',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack id for the associated data.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_name='packData',
        value_type=TunableTuple(
            description=
            '\n            Each pack will have a set icons and can have an optional filter \n            string for use in Build/CAS and an optional Credits Title\n            ',
            export_class_name='TunablePackDataTuple',
            credits_title=TunableLocalizedString(
                description=
                '\n                The title used in the credits dropdown to select this packs credits.\n                If set, there must be a creditsxml file for this pack\n                in Assets/InGame/UI/Flash/data/\n                ',
                allow_none=True),
            filter_name=TunableLocalizedString(
                description=
                '\n                The name to used to describe the pack in CAS and BuildBuy filters.\n                If set, this pack will appear in the filter list.\n                ',
                allow_none=True),
            pack_type=TunableEnumEntry(
                description=
                '\n                Which type of pack is this.\n                ',
                tunable_type=PackTypes,
                default=PackTypes.BASE),
            icon_32=TunableResourceKey(
                description=
                '\n                Pack icon. 32x32.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_64=TunableResourceKey(
                description=
                '\n                Pack icon. 64x64.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_128=TunableResourceKey(
                description=
                '\n                Pack icon. 128x128.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_owned=TunableIcon(
                description=
                '\n                Pack icon that is displayed in the main menu\n                pack display when the player owns that pack.\n                ',
                allow_none=True),
            icon_unowned=TunableIcon(
                description=
                '\n                Pack icon that is displayed in the main menu\n                pack display when the player does not own that pack.\n                ',
                allow_none=True),
            webstore_id=Tunable(
                description=
                '\n                web store pack specific url identifier\n\t\t\t\t',
                tunable_type=str,
                default=None),
            region_list=TunableList(
                description=
                '\n                A list of tuples that describe each new region in the pack.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains metadata for a world select region.\n                    ',
                    export_class_name='TunablePackRegionTuple',
                    region_resource=TunableRegionDescription(
                        description=
                        '\n                            Reference to the region description catalog resource associated with this region\n                        ',
                        pack_safe=True),
                    is_player_facing=Tunable(
                        description=
                        '\n                            Whether to display this region in world select when the user does not own the associated pack\n                        ',
                        tunable_type=bool,
                        default=False),
                    region_name=TunableLocalizedString(
                        description=
                        '\n                        Localized name of region.\n                        ',
                        allow_none=True),
                    region_description=TunableLocalizedString(
                        description=
                        '\n                        Localized description of region.\n                        ',
                        allow_none=True),
                    overlay_layer=TunableResourceKey(
                        description=
                        '\n                        Hero image displayed on mouse over of region in\n\t\t\t\t\t\tworld selection UI.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE,
                        allow_none=True),
                    parallax_layers=TunableList(
                        description=
                        '\n                        Images used for scrolling parallax layers for region\n                        in world selection UI. Max number of images = 5.\n                        ',
                        maxlength=5,
                        tunable=TunableResourceKey(
                            resource_types=sims4.resources.CompoundTypes.IMAGE
                        )),
                    is_destination_region=Tunable(
                        description=
                        '\n                        Whether this region is a destination world.\n                        ',
                        tunable_type=bool,
                        default=False))),
            promo_cycle_images=TunableList(
                description=
                '\n                A list of promo screenshots and titles to display in the \n                Pack Detail panel.\n                ',
                tunable=PromoCycleImagesTuning(
                    description=
                    '\n                    Screenshots and label displayed in the Pack Detail Panel\n                    and Pack Preview Panel.\n                    '
                )),
            short_description=TunableLocalizedString(
                description=
                '\n                Short description of the pack meant to be displayed in \n                a tooltip.\n                ',
                allow_none=True)),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='PackSpecificDataTuple')
    BUNDLE_SPECIFIC_DATA = TunableMapping(
        description=
        '\n        Mapping from an MTX Bundle to its associated data. This is for bundles that\n        should appear in the ui, but are not packs. This includes main menu icons,\n        description, and the action associated with that bundle.\n        ',
        key_type=TunableMTXBundle(
            description=
            '\n            The MTX bundle id for the associated data.\n            ',
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            Each bundle has icons and a description, as well as an\n            data for the action performed when the bundle is interacted \n            with either the PromotionDialog or the PackDisplayPanel.\n            ',
            bundle_name=TunableLocalizedString(
                description=
                '\n                Name used in pack detail panel and main menu. If empty,\n                we fall back to using the MTX product name.\n                ',
                allow_none=True),
            icon_owned=TunableIcon(
                description=
                '\n                Bundle icon that is displayed in the main menu\n                pack display when the player is entitled to that bundle.\n                '
            ),
            icon_unowned=TunableIcon(
                description=
                '\n                Bundle icon that is displayed in the main menu\n                pack display when the player is not entitled to that bundle.\n                '
            ),
            short_description=TunableLocalizedString(
                description=
                '\n                Short description of the bundle meant to be displayed in \n                a tooltip.\n                '
            ),
            action=TunableVariant(
                description=
                '\n                The action that should be performed when this bundle is interacted with\n                in either the PromotionDialog or the PackDisplayPanel.\n                ',
                url=Tunable(
                    description=
                    '\n                    External url to open from PackDisplayPanel.\n                    ',
                    tunable_type=str,
                    default=None),
                promo_data=TunableTuple(
                    description=
                    '\n                    Data that populates PromotionDialog.\n                    ',
                    title=TunableLocalizedString(
                        description=
                        '\n                        Title of the promotion.\n                        '
                    ),
                    text=TunableLocalizedString(
                        description=
                        '\n                        Text describing the promotion.\n                        '
                    ),
                    image=TunableIcon(
                        description=
                        '\n                        Image displayed in the promotion dialog.\n                        '
                    ),
                    legal_text=TunableLocalizedString(
                        description=
                        '\n                        Legal text required for this promotion.\n                        ',
                        allow_none=True),
                    export_class_name='TunablePromoDataTuple'),
                default='url'),
            export_class_name='TunableBundleDataTuple'),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='BundleSpecificDataTuple')
    PACK_RELEASE_ORDER = TunableList(
        description='\n        List of Pack Ids in release order.\n        ',
        tunable=TunableEnumEntry(
            description='\n            A pack Id.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        export_modes=(ExportModes.ClientBinary, ))
    CHALLENGE_DATA = TunableList(
        description=
        '\n        List of challenge event data for engagement challenge notification UI.\n        ',
        tunable=TunableTuple(
            description=
            '\n            Data for each engagement challenge event.\n            ',
            export_class_name='TunableChallengeNotificationTuple',
            challenge_list=TunableList(
                description=
                '\n                A list of tuples that describe each challenge.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains data for a challenge.\n                    ',
                    export_class_name='TunableChallengeDataTuple',
                    challenge_description=TunableLocalizedString(
                        description=
                        '\n                        The description of the challenge.\n                        ',
                        allow_none=True),
                    challenge_name=TunableLocalizedString(
                        description=
                        '\n                        The name of the challenge.\n                        ',
                        allow_none=True),
                    image=TunableResourceKey(
                        description=
                        '\n                        The main image displayed for challenge info.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    info_link=Tunable(
                        description=
                        '\n                        The url link to page for more info on a challenge.\n                        ',
                        tunable_type=str,
                        default='',
                        allow_empty=True),
                    event_display=TunableTuple(
                        description=
                        '\n                        Display data for a challenge event.\n                        ',
                        export_class_name='TunableChallengeEventDisplayTuple',
                        event_icon=TunableIcon(
                            description=
                            '\n                            An icon to use for the challenge event.\n                            ',
                            allow_none=True),
                        event_title=TunableLocalizedString(
                            description=
                            '\n                            Title to display.  If not provided, challenge name will be used.\n                            ',
                            allow_none=True),
                        end_time=TunableTuple(
                            description=
                            '\n                            Date and time (UTC) for when the challenge event is expected to end.\n                            This is currently used to compute the time remaining in the UI.\n                            ',
                            display_name='End Time (UTC)',
                            export_class_name='TunableChallengeDateTuple',
                            year=TunableRange(
                                description=
                                '\n                                Year\n                                ',
                                tunable_type=int,
                                default=2016,
                                minimum=2014),
                            month=TunableRange(
                                description=
                                '\n                                Month\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=1,
                                maximum=12),
                            day=TunableRange(
                                description=
                                '\n                                Day\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=1,
                                maximum=31),
                            hour=TunableRange(
                                description=
                                '\n                                Hour (24-hour)\n                                ',
                                tunable_type=int,
                                default=0,
                                minimum=0,
                                maximum=23),
                            minute=TunableRange(
                                description=
                                '\n                                Minute\n                                ',
                                tunable_type=int,
                                default=0,
                                minimum=0,
                                maximum=59)),
                        activity_icon=TunableIcon(
                            description=
                            '\n                            Icon to display beside the activity progress bar.\n                            ',
                            allow_none=True),
                        activity_progress_text=TunableLocalizedString(
                            description=
                            "\n                            Status text for when the player is still making progress towards\n                            the challenge goal.  This is currently displayed on a tooltip.\n                            A CSS class of 'timeremaining' will have its color changed\n                            when the event is close to ending.\n                            The following tokens are available:\n                            0 - Number: Current collection progress, if available.\n                            1 - Number: Collection goal, if available.\n                            2 - Number: Hours remaining.\n                            3 - Number: Days remaining.\n                            ",
                            allow_none=True),
                        activity_progress_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the progress text.\n                            ',
                            allow_none=True),
                        activity_complete_text=TunableLocalizedString(
                            description=
                            "\n                            Status text for when the player has met the challenge goal.\n                            This is currently displayed on a tooltip.\n                            If not specified, the in-progress text will be used.\n                            A CSS class of 'timeremaining' will have its color changed\n                            when the event is close to ending.\n                            The following tokens are available (same as the in-progress text):\n                            0 - Number: Current collection progress, if available.\n                            1 - Number: Collection goal, if available.\n                            2 - Number: Hours remaining.\n                            3 - Number: Days remaining.\n                            ",
                            allow_none=True),
                        activity_complete_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the challenge complete text.\n                            If not specified, the in-progress icon will be used.\n                            ',
                            allow_none=True),
                        community_progress_text=TunableLocalizedString(
                            description=
                            "\n                            Status text describing the community's progress.\n                            This is currently displayed on a tooltip.\n                            This text is displayed even when challenges do not have\n                            community goals.\n                            Two Number tokens are available:\n                            0 - Current community collection progress.\n                            1 - Community collection goal, if any.\n                            ",
                            allow_none=True),
                        community_progress_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the community status text.\n                            ',
                            allow_none=True),
                        community_complete_text=TunableLocalizedString(
                            description=
                            '\n                            Status text for when the community has met the challenge goal.\n                            This text is only used when a goal is defined.\n                            If not specified, the in-progress status text will be used.\n                            Two Number tokens are available:\n                            0 - Current community collection progress.\n                            1 - Community collection goal, if any.\n                            ',
                            allow_none=True),
                        community_complete_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the community challenge complete text.\n                            ',
                            allow_none=True),
                        community_goal_amount=Tunable(
                            description=
                            '\n                            Optional collection goal for the community to reach.\n                            ',
                            tunable_type=int,
                            default=0)),
                    collection_id=TunableEnumEntry(
                        description=
                        "\n                        A CollectionIdentifier that is associated with this\n                        challenge. This is used by the UI to tie a collectible \n                        with this challenge.\n                        \n                        Use the default of Unindentified for challenges that\n                        aren't associated with a particular collection.\n                        ",
                        tunable_type=CollectionIdentifier,
                        default=CollectionIdentifier.Unindentified,
                        export_modes=ExportModes.All),
                    reward_items=TunableList(
                        description=
                        '\n                        A list of tuples that describe rewards for challenge.\n                        ',
                        tunable=TunableTuple(
                            description=
                            '\n                            A tuple that contains data for a challenge reward item.\n                            ',
                            export_class_name='TunableChallengeRewardTuple',
                            reward_icon=TunableResourceKey(
                                description=
                                '\n                                The icon of reward item.\n                                ',
                                resource_types=sims4.resources.CompoundTypes.
                                IMAGE),
                            reward_name=TunableLocalizedString(
                                description=
                                '\n                                The name of reward item.\n                                ',
                                allow_none=True))))),
            challenge_subtitle=TunableLocalizedString(
                description=
                '\n                The subtitle text to be displayed in notification UI.\n                ',
                allow_none=True),
            challenge_title=TunableLocalizedString(
                description=
                '\n                The title text to be displayed in notification UI.\n                ',
                allow_none=True),
            switch_name=Tunable(
                description=
                '\n                Server switch name to check whether challenge is active.\n                ',
                tunable_type=str,
                default='',
                allow_empty=True)),
        export_modes=(ExportModes.ClientBinary, ))
    PLATFORM_STRING_REPLACEMENTS = TunableList(
        description=
        '\n        A list of strings that will be swapped out when in use on different \n        platforms. Each entry contains the original and replacement LocKey, the platforms\n        to perform the swap on, and the input method that is in use when the\n        LocKey is used.\n        ',
        tunable=TunableTuple(
            original_string=TunableLocalizedString(
                description=
                '\n                The string that will be replaced or ignored.\n                '
            ),
            replacement_string=OptionalTunable(
                description=
                '\n                The string that will be used in place of original_string. If\n                omitted, original_string will simply be ignored entirely.\n                ',
                tunable=TunableLocalizedString()),
            platform=TunableEnumEntry(
                description=
                '\n                The platforms on which the string will be replaced.\n                ',
                tunable_type=Platform,
                default=Platform.CONSOLE),
            input_method=TunableEnumEntry(
                description=
                '\n                The input method that should be in use when attempting to replace\n                the original_string.\n                ',
                tunable_type=InputMethod,
                default=InputMethod.ANY),
            export_modes=ExportModes.ClientBinary,
            export_class_name='PlatformStringReplacementTuple'))
    SCALING = TunableList(
        description=
        '\n        Defines a min/max ui scaling value for a screen resolution.\n        ',
        tunable=TunableTuple(
            screen_width=Tunable(
                description=
                '\n                Provide an integer value.\n                ',
                tunable_type=int,
                default=0),
            screen_height=Tunable(
                description=
                '\n                Provide an integer value.\n                ',
                tunable_type=int,
                default=0),
            scale_max=Tunable(
                description=
                '\n                Provide a float value.\n                ',
                tunable_type=float,
                default=1),
            scale_min=Tunable(
                description=
                '\n                Provide a float value.\n                ',
                tunable_type=float,
                default=1),
            export_modes=ExportModes.ClientBinary,
            export_class_name='UIScaleTuple'))
    CG_CHALLENGE_DATAS = TunableList(
        description="\n        A list of a challenge's data.\n        ",
        tunable=TunableTuple(
            cg_challenge_hashtag=TunableLocalizedString(
                description=
                '\n                Hashtag of this challenge\n                '
            ),
            cg_challenge_name=TunableLocalizedString(
                description=
                '\n                Name of this challenge\n                '),
            export_modes=ExportModes.ClientBinary,
            export_class_name='CGChallengeTuning'))
    DEFAULT_OVERLAY_MAP = TunableMapping(
        description=
        '\n        This is a mapping of MapOverlayEnum -> List of MapOverlayEnums. The key\n        is used as the layer to be shown when no other overlays are present.\n        The value is a list of overlay types that would result in the default\n        layer being turned off if both are active.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            This is the OverlayType that acts as the default for the grouping\n            of OverlayTypes.\n            ',
            tunable_type=MapOverlayEnum,
            default=MapOverlayEnum.NONE),
        value_type=TunableList(
            description=
            '\n            A list of OverlayTypes, that if turned on would result in the\n            default OverlayType being shut off.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The OverlayType that causes the default value to turn off.\n                ',
                tunable_type=MapOverlayEnum,
                default=MapOverlayEnum.NONE)),
        export_modes=ExportModes.All,
        tuple_name='OverlayDefaultData')
Example #12
0
class CasStoriesQuestion(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.CAS_STORIES_QUESTION)):
    INSTANCE_TUNABLES = {'text': TunableLocalizedStringFactory(description='\n            The text of this question.\n            ', export_modes=ExportModes.ClientBinary, tuning_group=GroupNames.UI), 'icon': TunableIcon(description='\n            The icon for this question.\n            ', export_modes=ExportModes.ClientBinary, tuning_group=GroupNames.UI), 'possible_answers': TunableList(description='\n            Answers to this question.\n            ', tunable=CasStoriesAnswer.TunableReference(), export_modes=ExportModes.ClientBinary), 'pack': TunableEnumEntry(description='\n            The pack with which this question is associated. Will affect when\n            and how frequently this answer is selected by CAS Stories.\n            ', tunable_type=Pack, default=Pack.BASE_GAME, export_modes=ExportModes.ClientBinary)}
Example #13
0
class UniversityMajor(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(Types.UNIVERSITY_MAJOR)):
    INSTANCE_TUNABLES = {'courses': TunableList(description='\n            List of courses, in order, for this major\n            ', tunable=UniversityCourseData.TunableReference(), minlength=1), 'acceptance_score': TunableTuple(description='\n            Score requirement to be accepted in this major as prestige degree.\n            ', score=TunableMultiplier.TunableFactory(description='\n                Define the base score and multiplier to calculate acceptance\n                score of a Sim.\n                '), score_threshold=TunableThreshold(description='\n                The threshold to perform against the score to see if a Sim \n                can be accepted in this major.\n                ')), 'display_name': TunableLocalizedString(description="\n            The major's name.\n            ", tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'display_description': TunableLocalizedString(description="\n            The major's description.\n            ", tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'icons': TunableTuple(description='\n            Display icons for this major.\n            ', icon=TunableIcon(description="\n                The major's icon.\n                "), icon_prestige=TunableIcon(description="\n                The major's prestige icon.\n                "), icon_high_res=TunableIcon(description="\n                The major's high resolution icon.\n                "), icon_prestige_high_res=TunableIcon(description="\n                The major's prestige high resolution icon.\n                "), export_class_name='UniversityMajorIconTuple', tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'major_benefit_map': TunableMapping(description='\n            University specific major benefit description. Each university can \n            have its own description defined for this University Major.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableLocalizedString(description='\n                Major benefit description.\n                '), tuple_name='UniversityMajorBenefitMapping', tuning_group=GroupNames.UI, export_modes=ExportModes.All), 'graduation_reward': TunableMapping(description='\n            Loot on graduation at each university for each GPA threshold\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableList(description='\n                Loot for each GPA range (lower bound inclusive, upper bound\n                exclusive.\n                ', tunable=TunableTuple(gpa_range=TunableInterval(description='\n                        GPA range to receive this loot.\n                        Lower bound inclusive, upper bound exclusive.\n                        ', tunable_type=float, default_lower=0, default_upper=10), loot=TunableList(tunable=LootActions.TunableReference(description='\n                            The loot action applied.\n                            ', pack_safe=True))))), 'career_tracks': TunableList(description='\n            List of career tracks for which the UI will indicate this major\n            will provide benefit.  Is not used to actually provide said benefit.\n            ', tunable=TunableReference(description='\n                These are the career tracks that will benefit from this major.\n                ', manager=services.get_instance_manager(sims4.resources.Types.CAREER_TRACK), pack_safe=True), tuning_group=GroupNames.UI, export_modes=ExportModes.ClientBinary, unique_entries=True)}

    @classmethod
    def graduate(cls, sim_info, university, gpa):
        resolver = SingleSimResolver(sim_info)
        if university in cls.graduation_reward:
            for grad_reward in cls.graduation_reward[university]:
                if grad_reward.gpa_range.lower_bound <= gpa < grad_reward.gpa_range.upper_bound:
                    for loot_action in grad_reward.loot:
                        loot_action.apply_to_resolver(resolver)

    @classmethod
    def get_sim_acceptance_score(cls, sim_info):
        resolver = SingleSimResolver(sim_info)
        return cls.acceptance_score.score.get_multiplier(resolver)

    @classmethod
    def can_sim_be_accepted(cls, sim_info):
        sim_score = cls.get_sim_acceptance_score(sim_info)
        return cls.acceptance_score.score_threshold.compare(sim_score)
Example #14
0
    class _UniversityDynamicSignView(HasTunableSingletonFactory,
                                     AutoFactoryInit):
        class _LiteralString(HasTunableSingletonFactory, AutoFactoryInit):
            FACTORY_TUNABLES = {
                'text':
                TunableLocalizedStringFactory(
                    description=
                    '\n                    The text to be shown.\n                    \n                    * Token 0: Sim\n                    '
                )
            }

            def get_string(self, sim_info):
                return self.text(sim_info)

        class _FromSimInfo(HasTunableSingletonFactory, AutoFactoryInit):
            FACTORY_TUNABLES = {
                'university':
                TunableReference(
                    description=
                    '\n                    The university to get the data from.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.UNIVERSITY)),
                'info_type':
                TunableEnumEntry(
                    description=
                    '\n                    The type of the university info.\n                    ',
                    tunable_type=UniversityInfoType,
                    default=UniversityInfoType.INVALID,
                    invalid_enums=(UniversityInfoType.INVALID,
                                   UniversityInfoType.ORGANIZATIONS)),
                'fallback_string':
                TunableLocalizedStringFactory(
                    description=
                    '\n                    The string to be shown when failing to find string from\n                    the tuned sim degree info.\n                    \n                    * Token 0: Sim\n                    '
                )
            }

            def get_string(self, sim_info):
                degree_tracker = sim_info.degree_tracker
                if degree_tracker is None:
                    logger.error(
                        'Trying to perform UniversityDynamicSignView op on sim {} with no degree tracker.',
                        sim_info)
                    return
                uni = self.university
                manager = services.get_instance_manager(
                    sims4.resources.Types.UNIVERSITY_MAJOR)
                degree_ids = degree_tracker.get_available_degrees_to_enroll()[
                    uni.guid64]
                bullet_points = ()
                if self.info_type == UniversityInfoType.PRESTIGE_DEGREES:
                    bullet_points = (manager.get(i).display_name
                                     for i in degree_ids
                                     if i in uni.prestige_degree_ids)
                elif self.info_type == UniversityInfoType.NON_PRESTIGE_DEGREES:
                    bullet_points = (manager.get(i).display_name
                                     for i in degree_ids
                                     if i in uni.non_prestige_degree_ids)
                final_string = LocalizationHelperTuning.get_bulleted_list(
                    None, *bullet_points)
                if final_string is None:
                    final_string = self.fallback_string(sim_info)
                return final_string

        class _FromUniversityInfo(HasTunableSingletonFactory, AutoFactoryInit):
            FACTORY_TUNABLES = {
                'university':
                TunableReference(
                    description=
                    '\n                    The university to get the data from.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.UNIVERSITY)),
                'info_type':
                TunableEnumEntry(
                    description=
                    '\n                    The type of the university info.\n                    ',
                    tunable_type=UniversityInfoType,
                    default=UniversityInfoType.INVALID,
                    invalid_enums=(UniversityInfoType.INVALID, ))
            }

            def get_string(self, _):
                bullet_points = ()
                if self.info_type == UniversityInfoType.PRESTIGE_DEGREES:
                    bullet_points = (d.display_name
                                     for d in self.university.prestige_degrees)
                elif self.info_type == UniversityInfoType.NON_PRESTIGE_DEGREES:
                    bullet_points = (
                        d.display_name
                        for d in self.university.non_prestige_degrees)
                elif self.info_type == UniversityInfoType.ORGANIZATIONS:
                    bullet_points = (o.display_name()
                                     for o in self.university.organizations
                                     if not o.hidden)
                return LocalizationHelperTuning.get_bulleted_list(
                    None, *bullet_points)

        FACTORY_TUNABLES = {
            'title':
            TunableLocalizedString(
                description=
                '\n                The title to be shown on top of view.\n                '
            ),
            'display_image':
            TunableResourceKey(
                description=
                '\n                 The image for this view display.\n                 ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            'background_image':
            TunableResourceKey(
                description=
                '\n                 The background image for this view display.\n                 ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            'sub_infos':
            TunableList(
                description=
                '\n                The sub info to be shown on the bottom of the view.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A single info.\n                    ',
                    name=TunableLocalizedString(
                        description=
                        '\n                        The name of this info.\n                        '
                    ),
                    desc=TunableVariant(
                        description=
                        '\n                        The description of this info.\n                        ',
                        literal=_LiteralString.TunableFactory(),
                        from_sim_info=_FromSimInfo.TunableFactory(),
                        from_university_info=_FromUniversityInfo.
                        TunableFactory(),
                        default='literal'),
                    icon=TunableIcon(
                        description=
                        '\n                        The Icon that represents this info.\n                        '
                    )))
        }

        def perform(self, subject, target, resolver):
            if subject is None:
                logger.error(
                    'Trying to perform UniversityDynamicSignView op but subject is None. Resolver {}.',
                    resolver)
                return
            if not subject.is_sim:
                logger.error(
                    'Trying to perform UniversityDynamicSignView op but subject {} is not Sim. Resolver {}.',
                    subject, resolver)
                return
            sign_info = UI_pb2.DynamicSignView()
            sign_info.name = self.title
            sign_info.image = sims4.resources.get_protobuff_for_key(
                self.display_image)
            sign_info.background_image = sims4.resources.get_protobuff_for_key(
                self.background_image)
            for sub_info in self.sub_infos:
                with ProtocolBufferRollback(
                        sign_info.activities) as activity_msg:
                    activity_msg.name = sub_info.name
                    activity_msg.description = sub_info.desc.get_string(
                        subject.sim_info)
                    activity_msg.icon = create_icon_info_msg(
                        IconInfoData(sub_info.icon))
            distributor = Distributor.instance()
            distributor.add_op(
                subject.sim_info,
                GenericProtocolBufferOp(Operation.DYNAMIC_SIGN_VIEW,
                                        sign_info))
Example #15
0
def get_display_mixin(has_description=False,
                      has_icon=False,
                      has_tooltip=False,
                      use_string_tokens=False,
                      export_modes=(),
                      enabled_by_default=False):
    tunable_localized_string_type = TunableLocalizedStringFactory if use_string_tokens else TunableLocalizedString
    export_to_client = ExportModes.ClientBinary in export_modes
    display_properties = {
        'instance_display_name':
        OptionalTunable(
            description=
            '\n            If enabled, specify a display name for this instance.\n            ',
            tunable=tunable_localized_string_type(
                description=
                "\n                The instance's name.\n                "),
            enabled_by_default=True,
            export_modes=export_modes,
            enabled_name='enabled_display_name'
            if export_to_client else 'enabled')
    }
    if has_description:
        display_properties['instance_display_description'] = OptionalTunable(
            description=
            '\n            If enabled, specify a display description for this instance.\n            ',
            tunable=tunable_localized_string_type(
                description=
                "\n                The instance's description. \n                "
            ),
            enabled_by_default=True,
            export_modes=export_modes,
            enabled_name='enabled_display_description'
            if export_to_client else 'enabled')
    if has_icon:
        display_properties['instance_display_icon'] = OptionalTunable(
            description=
            '\n            If enabled, specify a display icon for this instance.\n            ',
            tunable=TunableIcon(
                description=
                "\n                The instance's icon.\n                "),
            enabled_by_default=True,
            export_modes=export_modes,
            enabled_name='enabled_display_icon'
            if export_to_client else 'enabled')
    if has_tooltip:
        display_properties['instance_display_tooltip'] = OptionalTunable(
            description=
            '\n            If enabled, specify a display tooltip for this instance.\n            ',
            tunable=tunable_localized_string_type(
                description=
                "\n                The instance's tooltip. \n                "
            ),
            enabled_by_default=True,
            export_modes=export_modes,
            enabled_name='enabled_display_tooltip'
            if export_to_client else 'enabled')

    class _HasOptionalDisplayMixin:
        INSTANCE_TUNABLES = {
            '_display_data':
            OptionalTunable(
                description=
                '\n                If enabled, specify display data for this instance.\n                ',
                tunable=TunableTuple(
                    description=
                    "\n                    The instance's display data.\n                    ",
                    export_class_name='OptionalDisplayMixinTunable'
                    if export_to_client else 'TunableTuple',
                    **display_properties),
                tuning_group=GroupNames.UI,
                export_modes=export_modes,
                enabled_name='optional_display_mixin'
                if export_to_client else 'enabled',
                enabled_by_default=enabled_by_default)
        }

    TUNING_FIELD_PREFIX = 'instance_'
    for display_property_name in display_properties:
        if display_property_name.startswith(TUNING_FIELD_PREFIX):
            property_name = display_property_name[len(TUNING_FIELD_PREFIX):]
        else:
            property_name = display_property_name
        setattr(
            _HasOptionalDisplayMixin, property_name,
            classproperty(lambda c, attr_name=display_property_name: getattr(
                c._display_data, attr_name)
                          if c._display_data is not None else None))
    return _HasOptionalDisplayMixin
Example #16
0
class UseMusicProductionStationSuperInteraction(SuperInteraction):
    INSTANCE_TUNABLES = {
        'music_track_data_snippet':
        TunableMusicTrackDataSnippet(
            description=
            '\n            The reference to looping and fixed-length .propx files for the associated\n            music track.\n            '
        ),
        'channels':
        TunableMapping(
            description=
            '\n            A map of channel enums and their associated data. \n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The enum for a channel.\n                ',
                tunable_type=ChannelFlags,
                default=ChannelFlags.CHANNEL1),
            value_type=TunableTuple(
                description=
                '\n                Channel specific data.\n                ',
                channel_name=TunableLocalizedString(
                    description=
                    '\n                    The name to display for this channel. \n                    '
                ),
                channel_tests=TunableTestSet(
                    description=
                    "\n                   The tests to display this channel's remix mixer\n                   "
                ))),
        'turn_on_channel_display_name':
        TunableLocalizedStringFactory(
            description=
            '\n            The name to display for remix mixers that turn on a channel.\n            '
        ),
        'turn_on_channel_icon':
        TunableIcon(
            description=
            '\n            The icon to display in the pie menu for remix mixers that turn on a channel. \n            ',
            tuning_group=GroupNames.UI),
        'turn_off_channel_display_name':
        TunableLocalizedStringFactory(
            description=
            '\n            The name to display for remix mixers that turn off a channel. \n            '
        ),
        'turn_off_channel_icon':
        TunableIcon(
            description=
            '\n            The icon to display in the pie menu for remix mixers that turn off a channel. \n            ',
            tuning_group=GroupNames.UI),
        'audio_start_event':
        Tunable(
            description=
            '\n            The script event to listen for from animation so we know when to\n            start the music.\n            ',
            tunable_type=int,
            default=520),
        'audio_stop_event':
        Tunable(
            description=
            '\n            The script event to listen for from animation so we know when to\n            stop the music.\n            ',
            tunable_type=int,
            default=521)
    }

    def __init__(self, aop, context, *args, **kwargs):
        super().__init__(aop,
                         context,
                         *args,
                         exit_functions=(),
                         force_inertial=False,
                         additional_post_run_autonomy_commodities=None,
                         **kwargs)
        self._sound = None
        self._stored_audio_component = None

    def build_basic_content(self, sequence=(), **kwargs):
        self.store_event_handler(self._play_music_track,
                                 self.audio_start_event)
        self.store_event_handler(self._stop_music_track, self.audio_stop_event)
        sequence = super().build_basic_content(sequence, **kwargs)
        return build_critical_section_with_finally(sequence,
                                                   self._stop_music_track)

    def _play_music_track(self, event_data, *args, **kwargs):
        self._stored_audio_component = self.target.get_component(
            STORED_AUDIO_COMPONENT)
        if self._stored_audio_component is None:
            logger.error(
                '{} has no Stored Audio Component, which UseMusicProductionStationSuperInteraction requires for proper use.',
                self.target)
            return
        if self._sound is None:
            self._stored_audio_component.store_track(
                music_track_snippet=self.music_track_data_snippet)
            self._sound = self._stored_audio_component.play_looping_music_track(
                self.target)

    def _stop_music_track(self, event_data, *args, **kwargs):
        if self._sound is not None:
            self._sound.stop()
            self._sound = None

    def _exited_pipeline(self, *args, **kwargs):
        if self._stored_audio_component is not None:
            self._stored_audio_component.clear()
            self._stored_audio_component = None
        super()._exited_pipeline(*args, **kwargs)
Example #17
0
class AgingData(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'ages':
        TunableMapping(
            description=
            '\n            All available ages for this Sim, and any data associated with that\n            specific age.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The available age for the Sim.\n                ',
                tunable_type=Age,
                default=Age.ADULT,
                binary_type=EnumBinaryExportType.EnumUint32),
            value_type=TunableTuple(
                description=
                '\n                Any further data associated with this age.\n                ',
                transition=TunableAgingTransitionReference(
                    description=
                    '\n                    The transition data associated with this age, such as\n                    dialogs, notifications, durations, etc...\n                    ',
                    pack_safe=True),
                personality_trait_count=TunableRange(
                    description=
                    '\n                    The number of traits available to a Sim of this age.\n                    ',
                    tunable_type=int,
                    default=3,
                    minimum=0,
                    export_modes=ExportModes.All),
                cas_icon=TunableIcon(
                    description=
                    '\n                    Icon to be displayed in the ui for the age.\n                    ',
                    export_modes=ExportModes.ClientBinary),
                cas_icon_selected=TunableIcon(
                    description=
                    '\n                    Icon to be displayed in the UI for the age when buttons are\n                    selected.\n                    ',
                    export_modes=ExportModes.ClientBinary),
                cas_name=TunableLocalizedStringFactory(
                    description=
                    '\n                    The name to be displayed in the UI for the age.\n                    ',
                    export_modes=ExportModes.ClientBinary),
                export_class_name='AvailableAgeDataTuple'),
            minlength=1,
            tuple_name='AvailableAgeDataMapping'),
        'age_up_interaction':
        TunableReference(
            description=
            '\n            The default interaction that ages Sims up. This is called when Sims\n            auto-age or when the "Age Up" cheat is invoked.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            pack_safe=True),
        'old_age_interaction':
        TunableReference(
            description=
            '\n            The default interaction that transitions a Sim from old age to\n            death.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            pack_safe=True),
        'old_age_npc_death_type_fallback':
        TunableEnumEntry(
            description=
            "\n            Used if the Old Age Interaction is not a death interaction.  In that\n            case, the non-instanced NPCs are not running the interaction but also\n            can't get their death type from the interaction's tuning.  This value\n            is used as a fallback.  The NPC's death type set to this value, and \n            it will effectively become a ghost.\n            ",
            tunable_type=DeathType,
            default=DeathType.NONE,
            pack_safe=True),
        'bonus_days':
        TunableMapping(
            description=
            '\n            Specify how much bonus time is added to elder Sims\n            possessing these traits.\n            ',
            key_type=TunableReference(
                description=
                '\n                The trait associated with this modifier.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.TRAIT),
                pack_safe=True),
            value_type=Tunable(
                description=
                '\n                The modifier associated with this trait.\n                ',
                tunable_type=float,
                default=0))
    }

    def get_age_transition_data(self, age):
        return self.ages[age].transition

    def get_birth_age(self):
        return min(self.ages)

    def get_lifetime_duration(self, sim_info):
        total_lifetime = sum(
            age_data.transition.get_age_duration(sim_info)
            for age_data in self.ages.values())
        aging_service = services.get_aging_service()
        total_lifetime /= aging_service.get_speed_multiple(
            aging_service.aging_speed)
        return total_lifetime

    def get_lifetime_bonus(self, sim_info):
        lifetime_duration = self.get_lifetime_duration(sim_info)
        bonus_multiplier = sum(modifier
                               for (trait,
                                    modifier) in self.bonus_days.items()
                               if sim_info.has_trait(trait))
        return lifetime_duration * bonus_multiplier

    def get_personality_trait_count(self, age):
        age_data = self.ages.get(age, None)
        if age_data is None:
            raise ValueError('{} is not in {}'.format(age, self.ages))
        return age_data.personality_trait_count

    def get_next_age(self, age):
        ages = tuple(sorted(self.ages))
        for (current_age, next_age) in zip(ages, ages[1:]):
            if current_age <= age < next_age:
                return next_age
        raise ValueError('There is no age after {}'.format(age))

    def get_previous_age(self, age):
        ages = tuple(sorted(self.ages))
        for (previous_age, current_age) in zip(ages, ages[1:]):
            if previous_age < age <= current_age:
                return previous_age
Example #18
0
 def __init__(self, description='A grouping of headline update data.', **kwargs):
     super().__init__(description=description, icon=TunableIcon(description='\n                The icon that we will use for this update.\n                '), minimum_value=Tunable(description='\n                The minimum value that this update level will be used.\n                ', tunable_type=float, default=0.0), maximum_value=Tunable(description='\n                The maximum value that this update level will be used.\n                ', tunable_type=float, default=1.0), fx=TunableEnumEntry(description='\n                The fx on the flash timeline that should be used.\n                ', tunable_type=FXType, default=FXType.NO_EFFECT), **kwargs)
Example #19
0
class ScreenSlam(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'display_type': ScreenSlamDisplayVariant(), 'title': OptionalTunable(description='\n            Title of the screen slam.\n            ', tunable=TunableLocalizedStringFactory()), 'text': OptionalTunable(description='"\n            Text of the screen slam.\n            ', tunable=TunableLocalizedStringFactory()), 'icon': OptionalTunable(description=',\n            Icon to be displayed for the screen Slam.\n            ', tunable=TunableIcon()), 'audio_sting': OptionalTunable(description='\n            A sting to play at the same time as the screen slam.\n            ***Some screen slams may appear to play a sting, but the audio is\n            actually tuned on something else.  Example: On CareerLevel tuning\n            there already is a tunable, Promotion Audio Sting, to trigger a\n            sting, so one is not necessary on the screen slam.  Make sure to\n            avoid having to stings play simultaneously.***\n            ', tunable=TunablePlayAudio()), 'active_sim_only': Tunable(description='\n            If true, the screen slam will be only be shown if the active Sim\n            triggers it.\n            ', tunable_type=bool, default=True)}

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

    @classmethod
    def _verify_tuning_callback(cls):
        advertising_data_types = frozenset(
            cls.advertising_configuration.advertising_data_map.keys())
        advertising_types_with_mapped_names = frozenset(
            cls.advertising_name_map.keys())
        advertising_sort_ordered_types = frozenset(
            cls.advertising_name_map.keys())
        if advertising_data_types:
            if advertising_data_types != advertising_types_with_mapped_names:
                logger.error(
                    'Advertising type list {} does not match list of mapped names: {}',
                    advertising_data_types,
                    advertising_types_with_mapped_names)
            if advertising_data_types != advertising_sort_ordered_types:
                logger.error(
                    'Advertising type list {} does not sorted UI list types: {}',
                    advertising_data_types, advertising_sort_ordered_types)
        if cls.advertising_configuration.default_advertising_type is not None and cls.advertising_configuration.default_advertising_type not in advertising_types_with_mapped_names:
            logger.error(
                'Default advertising type {} is not in advertising name map',
                cls.default_advertising_type)
Example #22
0
class RankedStatistic(
        HasTunableReference,
        ProgressiveStatisticCallbackMixin,
        statistics.continuous_statistic_tuning.TunedContinuousStatistic,
        metaclass=HashedTunedInstanceMetaclass,
        manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)
):
    @classmethod
    def _verify_tuning_callback(cls):
        super()._verify_tuning_callback()
        ranks_tuned = [
            level_data for level_data in cls.event_data.values()
            if level_data.rank_up
        ]
        ranks_needed = len(ranks_tuned) + 1
        actual_ranks = len(cls.rank_tuning)
        tuned_rank_up_notifications = len(cls.rank_up_notification_tuning)
        tuned_rank_down_notifications = len(cls.rank_down_notification_tuning)
        if actual_ranks != ranks_needed:
            logger.error(
                '{} ranks have been enabled, but there is tuning for {} ranks in the rank_tuning. Please double check the tuning for {}',
                ranks_needed, actual_ranks, cls)
        if actual_ranks != tuned_rank_up_notifications:
            logger.error(
                'There are {} ranks tuned but {} rank up notifications tuned. These need to be the same. Please double check the tuning for {}',
                actual_ranks, tuned_rank_up_notifications, cls)
        if tuned_rank_down_notifications > 0 and actual_ranks != tuned_rank_down_notifications:
            logger.error(
                'There are {} ranks tuned but {} rank down notifications tuned. These need to be the same. Please double check the tuning for {}',
                actual_ranks, tuned_rank_down_notifications, cls)

    INSTANCE_TUNABLES = {
        'stat_name':
        TunableLocalizedString(
            description=
            '\n            Localized name of this statistic.\n            ',
            allow_none=True),
        'event_intervals':
        TunableList(
            description=
            '\n            The level boundaries for an event, specified as a delta from the\n            previous value.\n            ',
            tunable=Tunable(
                description=
                '\n                Points required to reach this level.\n                ',
                tunable_type=int,
                default=0),
            export_modes=ExportModes.All),
        'event_data':
        TunableMapping(
            description=
            '\n            The data associated with a specific tuned event. \n            \n            The Key is the event number as tuned in the event intervals.\n            \n            The value is a list of loots to apply when the event occurs and an\n            bool for whether or not to rank up the stat. \n            ',
            key_type=int,
            value_type=TunableTuple(
                description=
                '\n                The data associated with a tuned event from event_intervals.\n                ',
                rank_up=Tunable(
                    description=
                    "\n                    If checked then this event will cause the statistic to rank\n                    up and all that entails. Currently that will increment\n                    the rank count.\n                    \n                    There should be a rank up entry for each of the levels \n                    tuned, except the initial rank. We assume that you don't \n                    need to rank into the initial rank. This means you will \n                    need one more level tuned than number of rank up events\n                    found in this list.\n                    ",
                    tunable_type=bool,
                    default=False),
                loot=TunableList(
                    description=
                    '\n                    A list of loots to apply when this event happens. This loot\n                    is only applied the first time you reach a specific level.\n                    If you want the loot applied every time you reach a level\n                    (for instance after you decay to a previous level and then\n                    regain a level) please use the loot_always tuning.\n                    ',
                    tunable=TunableReference(
                        description=
                        '\n                        The loot to apply.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.ACTION),
                        class_restrictions=('LootActions',
                                            'RandomWeightedLoot'),
                        pack_safe=True)),
                tooltip=TunableLocalizedStringFactory(
                    description=
                    '\n                    The tooltip to display in the UI for each of the event\n                    lines. This is to be used for telling the user what loot \n                    they are going to get at an individual event.\n                    '
                ),
                level_down_loot=TunableList(
                    description=
                    '\n                    A list of loots to apply when the Sim loses enough points \n                    to level down.\n                    ',
                    tunable=LootActions.TunableReference(pack_safe=True)),
                tests=event_testing.tests.TunableTestSet(
                    description=
                    "\n                    Tests to run when reaching this level. If the tests don't \n                    pass then the value will be set back to min points for \n                    the rank before it. This means that the Sim won't be able\n                    to make any progress towards the rank with the failed\n                    tests.\n                    ",
                    export_modes=ExportModes.ServerXML),
                loot_always=TunableList(
                    description=
                    '\n                    This loot is always awarded on level up, regardless of \n                    whether or not this level has already been achieved or not.\n                    \n                    If you want the loot to only be applied the first time you\n                    reach a certain level then please use the loot tuning.\n                    ',
                    tunable=TunableReference(
                        description=
                        '\n                        The loot to award on level up.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.ACTION),
                        class_restrictions=('LootActions',
                                            'RandomWeightedLoot'),
                        pack_safe=True)),
                loot_always_on_load=TunableList(
                    description=
                    '\n                    This loot is always awarded when a sim loads with this\n                    level.\n                    ',
                    tunable=LootActions.TunableReference(pack_safe=True)),
                export_class_name='EventDataTuple'),
            tuple_name='TunableEventData',
            export_modes=ExportModes.All),
        'initial_rank':
        Tunable(description=
                '\n            The initial rank for this stat.\n            ',
                tunable_type=int,
                default=1,
                export_modes=ExportModes.All),
        'rank_tuning':
        TunableMapping(
            description=
            '\n            This is the tuning that is associated with a specific rank level \n            instead of each individual event level. When the rank has increased \n            the matching information will be retrieved from here and used.\n            \n            There needs to be an equal number of ranks tuned to match all of \n            the rank up events in event data plus an extra one for the \n            rank you start out on initially.\n            ',
            key_type=int,
            value_type=TunableTuple(
                description=
                '\n                A tuple of all the data for each Rank associated wit this\n                ranked statistic.\n                ',
                rank_name=TunableLocalizedString(
                    description=
                    "\n                    The rank's normal name.\n                    "
                ),
                icon=OptionalTunable(
                    description=
                    '\n                    If enabled then the Rank Statistic will have an icon \n                    associated with this Rank.\n                    ',
                    tunable=TunableResourceKey(
                        description=
                        '\n                        Icon to be displayed for the rank.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    enabled_by_default=True),
                rank_description=OptionalTunable(
                    description=
                    '\n                    When enabled this string will be used as the description\n                    for the rank.\n                    ',
                    tunable=TunableLocalizedString(
                        description=
                        "\n                        The rank's description.\n                        "
                    )),
                rank_short_name=OptionalTunable(
                    description=
                    '\n                    When enabled this string will be used as an alternate \n                    short name for the rank.\n                    ',
                    tunable=TunableLocalizedString(
                        description=
                        "\n                        The rank's short name.\n                        "
                    )),
                hide_in_ui=Tunable(
                    description=
                    '\n                    If checked, this rank will not be shown in some places in the UI (XP bars, Relationship tooltip, Gallery)\n                    ',
                    tunable_type=bool,
                    default=False),
                export_class_name='RankDataTuple'),
            tuple_name='TunableRankData',
            export_modes=ExportModes.All),
        'rank_down_notification_tuning':
        TunableMapping(
            description=
            '\n            A mapping of Rank to tuning needed to display all the notifications\n            when a Sim ranks down. \n            \n            The number of notifications tuned must match the number of items\n            in rank_tuning.\n            ',
            key_type=int,
            value_type=TunableTuple(
                description=
                '\n                A Tuple containing both the rank down screen slam and the rank\n                down notification to display.\n                ',
                show_notification_tests=event_testing.tests.TunableTestSet(
                    description=
                    '\n                    Tests that must be true when the we want to show notification.\n                    '
                ),
                rank_down_screen_slam=OptionalTunable(
                    description=
                    '\n                    Screen slam to show when Sim goes down to this rank level.\n                    Localization Tokens: Sim - {0.SimFirstName}, Rank Name - \n                    {1.String}, Rank Number - {2.Number}\n                    ',
                    tunable=ui.screen_slam.TunableScreenSlamSnippet()),
                rank_down_notification=OptionalTunable(
                    description=
                    '\n                    The notification to display when the Sim obtains this\n                    rank. The text will be provided two tokens: the Sim owning\n                    the stat and a number representing the 1-based rank\n                    level.\n                    ',
                    tunable=UiDialogNotification.TunableFactory(
                        locked_args={
                            'text_tokens': DEFAULT,
                            'icon': None,
                            'secondary_icon': None
                        })))),
        'rank_up_notification_tuning':
        TunableMapping(
            description=
            '\n            A mapping of Rank to tuning needed to display all the notifications\n            when a Sim ranks up. \n            \n            The number of notifications tuned must match the number of items\n            in rank_tuning.\n            ',
            key_type=int,
            value_type=TunableTuple(
                description=
                '\n                A Tuple containing both the rank up screen slam and the rank\n                up notification to display.\n                ',
                show_notification_tests=event_testing.tests.TunableTestSet(
                    description=
                    '\n                    Tests that must be true when the we want to show notification.\n                    '
                ),
                rank_up_screen_slam=OptionalTunable(
                    description=
                    '\n                    Screen slam to show when reaches this rank level.\n                    Localization Tokens: Sim - {0.SimFirstName}, Rank Name - \n                    {1.String}, Rank Number - {2.Number}\n                    \n                    This will only happen the first time a rank is reached.\n                    ',
                    tunable=ui.screen_slam.TunableScreenSlamSnippet()),
                rank_up_notification=OptionalTunable(
                    description=
                    '\n                    The notification to display when the Sim obtains this\n                    rank. The text will be provided two tokens: the Sim owning\n                    the stat and a number representing the 1-based rank\n                    level.\n                    \n                    This will only happen the first time a rank is reached. If\n                    you want to show a display on subsequent rank ups you can \n                    tune the re_rank_up_notifcation.\n                    ',
                    tunable=UiDialogNotification.TunableFactory(
                        locked_args={
                            'text_tokens': DEFAULT,
                            'icon': None,
                            'secondary_icon': None
                        })),
                re_rank_up_notification=OptionalTunable(
                    description=
                    '\n                    The notification to display when the Sim obtains this rank\n                    every time other than the first time. For instance if the\n                    Sim achieves rank 3, drops down to rank 2 because of decay,\n                    and then re-achieves rank 3, that is when this dialog will\n                    be displayed.\n                    \n                    If you want this dialog to be displayed the first time the\n                    Sim reaches a rank please tune rank_up_notification instead.\n                    ',
                    tunable=UiDialogNotification.TunableFactory(
                        locked_args={
                            'text_tokens': DEFAULT,
                            'icon': None,
                            'secondary_icon': None
                        })))),
        'tags':
        TunableList(
            description=
            '\n            The associated categories of the ranked statistic.\n            ',
            tunable=TunableEnumEntry(tunable_type=tag.Tag,
                                     default=tag.Tag.INVALID,
                                     pack_safe=True)),
        'icon':
        TunableIcon(
            description="\n            The ranked stat's icon.\n            ",
            allow_none=True),
        'initial_loot':
        TunableList(
            description=
            '\n            A list of loots to apply when the Sim first receives this ranked\n            statistic.\n            ',
            tunable=LootActions.TunableReference(pack_safe=True)),
        'min_decay_per_highest_level_achieved':
        TunableMapping(
            description=
            '\n            A mapping of highest level reached to the absolute minimum \n            that this Ranked Stat is allowed to decay to in ranks.\n            ',
            key_type=int,
            value_type=TunableRange(
                description=
                '\n                The lowest level this stat can decay to based on the associated\n                highest level reached.\n                ',
                tunable_type=int,
                minimum=1,
                default=1)),
        'associated_bucks_types':
        TunableList(
            description=
            '\n            A list of bucks types that are associated with this ranked stat.\n            These bucks types may have tuned data that is affected by ranking\n            up/down.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                A buck type that is associated with this ranked stat.\n                ',
                tunable_type=BucksType,
                default=BucksType.INVALID),
            unique_entries=True,
            export_modes=ExportModes.All),
        'zero_out_on_lock':
        Tunable(
            description=
            '\n            If checked, when this ranked stat is locked it will zero out\n            the value, highest_level, and bucks.\n            ',
            tunable_type=bool,
            default=True),
        'headline':
        OptionalTunable(
            description=
            '\n            If enabled when this relationship track updates we will display\n            a headline update to the UI.\n            ',
            tunable=TunableReference(
                description=
                '\n                The headline that we want to send down.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.HEADLINE)),
            tuning_group=GroupNames.UI),
        'send_stat_update_for_npcs':
        Tunable(
            description=
            "\n            If checked then whenever we attempt to send the ranked stat update\n            message it will be sent, even if the Sim is an NPC.\n            \n            NOTE: We don't want to mark very many of the stats like this. This \n            is being done to make sure that Fame gets sent so we don't have\n            to request Fame when building the tooltip for sims which could be\n            really slow.\n            ",
            tunable_type=bool,
            default=False),
        'center_bar_tooltip':
        Tunable(
            description=
            '\n            If true, always put motive panel ranked stat bar tooltip at the center.\n            If false, put tooltip on each increment mark instead.\n            ',
            tunable_type=bool,
            default=False,
            export_modes=ExportModes.All),
        'visible':
        Tunable(
            description=
            '\n            Whether or not statistic should be sent to client.\n            \n            NOTE: Please work with your UI engineering partner to determine if this \n            should be True. If False, for performance reasons, \n            the stat will be removed from the sim if their\n            current value matches the default convergence value. \n            ',
            tunable_type=bool,
            default=False,
            export_modes=ExportModes.All)
    }
    REMOVE_INSTANCE_TUNABLES = ('min_value_tuning', 'max_value_tuning')

    def __init__(self, tracker):
        self._rank_level = self.initial_rank
        self.highest_level = 0
        super().__init__(tracker, self.initial_value)
        self._current_event_level = 0
        self.previous_event_level = 0
        self._notifications_disabled = False
        self._initial_loots_awarded = False
        self._suppress_level_up_telemetry = False

    @constproperty
    def is_ranked():
        return True

    @property
    def rank_level(self):
        return self._rank_level

    @property
    def process_non_selectable_sim(self):
        return True

    @rank_level.setter
    def rank_level(self, value):
        self._rank_level = value
        services.get_event_manager().process_event(
            TestEvent.RankedStatisticChange,
            sim_info=self.tracker.owner.sim_info)

    @property
    def highest_rank_achieved(self):
        rank_level = self.initial_rank
        for i in range(1, self.highest_level + 1):
            if self.event_data.get(i).rank_up:
                rank_level += 1
        return rank_level

    @property
    def is_visible(self):
        if self.tracker is None or not self.tracker.owner.is_sim:
            return False
        return self.visible

    @property
    def instance_required(self):
        if self.is_visible:
            return True
        return super().instance_required

    def increase_rank_level(self, new_rank=True, from_add=False):
        self.rank_level += 1
        self._on_rank_up(new_rank=new_rank, from_add=from_add)

    def increase_rank_levels(self, levels):
        start_level = self.rank_level
        self.rank_level = start_level + levels
        self.send_rank_change_update_message(start_level, start_level + levels)

    def decrease_rank_level(self):
        self.rank_level = max(self.rank_level - 1, 0)
        self._on_rank_down()

    def _on_rank_up(self, new_rank=True, from_add=False):
        current_rank = self.rank_level
        self.send_rank_change_update_message(current_rank - 1, current_rank)
        sim_info = self.tracker.owner.sim_info
        rank_data = self.rank_tuning.get(current_rank)
        rank_up_data = self.rank_up_notification_tuning.get(current_rank)
        if rank_data is None:
            logger.error(
                'Sim {}: {} is trying to rank up to level {} but there is no rank tuning.',
                sim_info, self, current_rank)
            return
        if not from_add and (sim_info.is_selectable
                             and rank_up_data is not None
                             ) and self.can_show_notification(rank_up_data):
            icon_override = None if rank_data.icon is None else IconInfoData(
                icon_resource=rank_data.icon)
            if new_rank:
                self._show_initial_rank_up_notifications(
                    sim_info, current_rank, rank_data, rank_up_data,
                    icon_override)
            else:
                self._show_re_rank_up_notifications(sim_info, current_rank,
                                                    rank_data, rank_up_data,
                                                    icon_override)

    def _show_initial_rank_up_notifications(self, sim_info, current_rank,
                                            rank_data, rank_up_data,
                                            icon_override):
        if rank_up_data.rank_up_notification is not None:
            notification = rank_up_data.rank_up_notification(
                sim_info, resolver=SingleSimResolver(sim_info))
            notification.show_dialog(
                icon_override=icon_override,
                secondary_icon_override=IconInfoData(obj_instance=sim_info),
                additional_tokens=(current_rank, ))
        if rank_up_data.rank_up_screen_slam is not None:
            rank_up_data.rank_up_screen_slam.send_screen_slam_message(
                sim_info, sim_info, rank_data.rank_name, current_rank)

    def _show_re_rank_up_notifications(self, sim_info, current_rank, rank_data,
                                       rank_up_data, icon_override):
        if rank_up_data.re_rank_up_notification is not None:
            notification = rank_up_data.re_rank_up_notification(
                sim_info, resolver=SingleSimResolver(sim_info))
            notification.show_dialog(
                icon_override=icon_override,
                secondary_icon_override=IconInfoData(obj_instance=sim_info),
                additional_tokens=(current_rank, ))

    def _on_rank_down(self):
        current_rank = self.rank_level
        self.send_rank_change_update_message(current_rank + 1, current_rank)
        sim_info = self.tracker.owner.sim_info
        rank_data = self.rank_tuning.get(current_rank)
        rank_down_data = self.rank_down_notification_tuning.get(current_rank)
        if rank_data is None:
            logger.error(
                'Sim {}: {} is trying to rank down to level {} but there is no rank tuning.',
                sim_info, self, current_rank)
            return
        if self.can_show_notification(rank_down_data):
            if rank_down_data.rank_down_notification is not None:
                notification = rank_down_data.rank_down_notification(
                    sim_info, resolver=SingleSimResolver(sim_info))
                icon_override = None if rank_data.icon is None else IconInfoData(
                    icon_resource=rank_data.icon)
                notification.show_dialog(icon_override=icon_override,
                                         secondary_icon_override=IconInfoData(
                                             obj_instance=sim_info),
                                         additional_tokens=(current_rank, ))
            if rank_down_data.rank_down_screen_slam is not None:
                rank_down_data.rank_down_screen_slam.send_screen_slam_message(
                    sim_info, sim_info, rank_data.rank_name, current_rank)
        for bucks_type in self.associated_bucks_types:
            bucks_tracker = BucksUtils.get_tracker_for_bucks_type(
                bucks_type, owner_id=self.tracker.owner.id)
            bucks_tracker.validate_perks(bucks_type, self.rank_level)

    def on_add(self):
        super().on_add()
        self.tracker.owner.sim_info.on_add_ranked_statistic()
        self.on_stat_event(self.highest_level,
                           self.get_user_value(),
                           from_add=True)
        self.previous_event_level = self.get_user_value()
        if self.tracker.owner.is_simulating:
            self.apply_initial_loot()

    @classmethod
    def get_level_list(cls):
        return list(cls.event_intervals)

    def on_initial_startup(self):
        super().on_initial_startup()
        self.decay_enabled = self.tracker.owner.is_selectable and not self.tracker.owner.is_locked(
            self)

    @staticmethod
    def _callback_handler(stat_inst):
        new_level = stat_inst.get_user_value()
        stat_inst.on_stat_event(stat_inst.previous_event_level, new_level)
        stat_inst.previous_event_level = new_level
        stat_inst.refresh_threshold_callback()

    def on_stat_event(self, old_level, new_level, from_add=False):
        batch_rank_levels = 0
        while old_level < new_level:
            old_level += 1
            event_data = self.event_data.get(old_level)
            if event_data is not None:
                if self.tracker.owner.is_simulating:
                    resolver = SingleSimResolver(self.tracker.owner)
                    is_new_level = old_level > self.highest_level
                    if is_new_level:
                        for loot in event_data.loot:
                            loot.apply_to_resolver(resolver)
                        self.highest_level = old_level
                    if event_data.rank_up:
                        self.increase_rank_level(new_rank=is_new_level,
                                                 from_add=from_add)
                    for loot in event_data.loot_always:
                        loot.apply_to_resolver(resolver)
                elif event_data.rank_up:
                    batch_rank_levels += 1
            if self.tracker.owner.is_npc:
                if not from_add:
                    self._handle_level_up_telemetry(old_level)
            self._handle_level_up_telemetry(old_level)
        if batch_rank_levels > 0:
            self.increase_rank_levels(batch_rank_levels)
        else:
            self.create_and_send_commodity_update_msg(is_rate_change=False)

    @contextlib.contextmanager
    def suppress_level_up_telemetry(self):
        if self._suppress_level_up_telemetry:
            yield None
        else:
            self._suppress_level_up_telemetry = True
            try:
                yield None
            finally:
                self._suppress_level_up_telemetry = False

    def _handle_level_up_telemetry(self, level):
        if not self._suppress_level_up_telemetry:
            with telemetry_helper.begin_hook(
                    ranked_stat_telemetry_writer,
                    TELEMETRY_HOOK_RANKED_STAT_LEVEL_UP) as hook:
                hook.write_guid(TELEMETRY_FIELD_RANKED_STAT_TYPE, self.guid64)
                hook.write_int(TELEMETRY_FIELD_RANKED_STAT_LEVEL, level)

    @sims4.utils.classproperty
    def max_value(cls):
        return cls.get_max_skill_value()

    @sims4.utils.classproperty
    def min_value(cls):
        return 0

    @sims4.utils.classproperty
    def best_value(cls):
        return cls.max_value

    @sims4.utils.classproperty
    def max_rank(cls):
        (_, rank) = cls.calculate_level_and_rank(cls.max_value)
        return rank

    @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
            if current_value < 0:
                return level
        return level + 1

    def can_show_notification(self, rank_data):
        if self._notifications_disabled:
            return False
        elif rank_data is not None and rank_data.show_notification_tests is not None:
            resolver = event_testing.resolver.SingleSimResolver(
                self.tracker.owner)
            result = rank_data.show_notification_tests.run_tests(resolver)
            if not result:
                return False
        return True

    def set_value(self,
                  value,
                  *args,
                  from_load=False,
                  interaction=None,
                  **kwargs):
        old_points = self.get_value()
        old_user_value = self.get_user_value()
        value_to_set = value
        if not from_load:
            value_to_set = self._get_valid_value(value, old_user_value)
        minimum_level = self._get_minimum_decay_level()
        value_to_set = max(value_to_set, minimum_level)
        super().set_value(value_to_set,
                          *args,
                          from_load=from_load,
                          interaction=interaction,
                          **kwargs)
        new_user_value = self.get_user_value()
        if not from_load:
            self._handle_level_down(old_user_value, new_user_value)
            sim_info = self._tracker._owner
            new_points = self.get_value()
            stat_type = self.stat_type
            if old_points == self.initial_value or old_points != new_points:
                services.get_event_manager().process_event(
                    TestEvent.StatValueUpdate,
                    sim_info=sim_info,
                    statistic=stat_type,
                    custom_keys=(stat_type, ))
        self.send_commodity_progress_msg(is_rate_change=False)
        self.send_change_update_message(value - old_points,
                                        from_load=from_load)
        self.previous_event_level = new_user_value
        self.refresh_threshold_callback()

    def _update_value(self):
        minimum_decay = self._get_minimum_decay_level()
        old_value = self._value
        old_user_value = self.convert_to_user_value(self._value)
        super()._update_value(minimum_decay_value=minimum_decay)
        new_value = self._value
        new_user_value = self.convert_to_user_value(self._value)
        self._handle_level_down(old_user_value, new_user_value)
        if old_user_value > new_user_value:
            self.previous_event_level = new_user_value
            self.refresh_threshold_callback()
        stat_type = self.stat_type
        if new_value > old_value:
            sim_info = self._tracker._owner if self._tracker is not None else None
            services.get_event_manager().process_event(
                TestEvent.StatValueUpdate,
                sim_info=sim_info,
                statistic=stat_type,
                custom_keys=(stat_type, ))

    def _get_minimum_decay_level(self):
        min_rank = self.min_decay_per_highest_level_achieved.get(
            self.highest_level, None)
        if min_rank is None:
            return 0
        points = self.points_to_rank(min_rank)
        return points

    def _handle_level_down(self, old_value, new_value):
        while new_value < old_value:
            event_data = self.event_data.get(old_value)
            if event_data is not None:
                resolver = SingleSimResolver(self.tracker.owner)
                for loot in event_data.level_down_loot:
                    loot.apply_to_resolver(resolver)
                if event_data.rank_up:
                    self.decrease_rank_level()
            old_value -= 1

    def get_next_rank_level(self):
        current_value = self.get_user_value()
        index = current_value + 1
        if index > len(self.event_data):
            return current_value
        while not self.event_data[index].rank_up:
            if index == len(self.event_data):
                break
            index += 1
        return index

    @constproperty
    def remove_on_convergence():
        return False

    def send_commodity_progress_msg(self, is_rate_change=True):
        self.create_and_send_commodity_update_msg(
            is_rate_change=is_rate_change)

    @classmethod
    def points_to_level(cls, event_level):
        level = 0
        running_sum = 0
        level_list = cls.get_level_list()
        while level < len(level_list):
            while level < event_level:
                running_sum += level_list[level]
                level += 1
        return running_sum

    @classmethod
    def points_to_rank(cls, rank_level):
        rank = cls.initial_rank
        level = 0
        running_sum = 0
        level_list = cls.get_level_list()
        while rank < rank_level:
            while level < len(level_list):
                event_data = cls.event_data.get(level)
                if event_data is not None:
                    if cls.event_data[level].rank_up:
                        rank += 1
                if rank < rank_level:
                    running_sum += level_list[level]
                level += 1
        return running_sum

    def points_to_current_rank(self):
        return self.points_to_rank(self.rank_level)

    def create_and_send_commodity_update_msg(self,
                                             is_rate_change=True,
                                             allow_npc=False,
                                             from_add=False):
        ranked_stat_msg = Commodities_pb2.RankedStatisticProgressUpdate()
        ranked_stat_msg.stat_id = self.guid64
        ranked_stat_msg.change_rate = self.get_change_rate()
        ranked_stat_msg.rank = self.rank_level
        difference = self.get_value() - self.points_to_current_rank()
        ranked_stat_msg.curr_rank_points = int(
            difference) if difference > 0 else 0
        send_sim_ranked_stat_update_message(self.tracker.owner,
                                            ranked_stat_msg,
                                            allow_npc=allow_npc
                                            or self.send_stat_update_for_npcs)

    @classmethod
    def send_commodity_update_message(cls, sim_info, old_value, new_value):
        commodity_tracker = sim_info.commodity_tracker
        if commodity_tracker is None:
            return
        stat_instance = commodity_tracker.get_statistic(cls)
        if stat_instance is None:
            return
        stat_instance.create_and_send_commodity_update_msg(is_rate_change=True)

    def send_change_update_message(self, amount, from_load=False):
        if from_load:
            return
        if self.headline is None:
            return
        sim = self.tracker.owner
        if sim.is_selectable:
            self.headline.send_headline_message(sim, amount)

    def send_rank_change_update_message(self, previous_rank, current_rank):
        msg = Commodities_pb2.RankedStatisticRankChangedUpdate()
        msg.stat_id = self.guid64
        msg.previous_rank = previous_rank
        msg.current_rank = current_rank
        send_sim_ranked_stat_change_rank_change_update_message(
            self.tracker.owner, msg)
        self.send_commodity_progress_msg()

    def on_sim_ready_to_simulate(self):
        level = self.get_user_value()
        event_data = self.event_data.get(level)
        if event_data is not None:
            resolver = SingleSimResolver(self.tracker.owner)
            for loot in event_data.loot_always_on_load:
                loot.apply_to_resolver(resolver)
        self.apply_initial_loot()

    def apply_initial_loot(self):
        if not self.initial_loot:
            return
        if self._initial_loots_awarded:
            return
        resolver = SingleSimResolver(self.tracker.owner)
        for loot in self.initial_loot:
            loot.apply_to_resolver(resolver)
        self._initial_loots_awarded = True

    def _get_valid_value(self, value, old_score):
        new_score = self.convert_to_user_value(value)
        if old_score <= new_score:
            resolver = SingleSimResolver(self.tracker.owner)
            while old_score <= new_score:
                old_score += 1
                event_data = self.event_data.get(old_score)
                if event_data is not None:
                    if not event_data.tests.run_tests(resolver=resolver):
                        points = self.points_to_level(old_score - 1)
                        return points
        return value

    def on_lock(self, action_on_lock):
        self._notifications_disabled = True
        should_zero_out = self.zero_out_on_lock or action_on_lock == StatisticLockAction.USE_MIN_VALUE_TUNING
        if should_zero_out:
            self.highest_level = 0
        super().on_lock(action_on_lock)
        if should_zero_out:
            self.reset_bucks()
        self._notifications_disabled = False

    def reset_bucks(self):
        for bucks_type in self.associated_bucks_types:
            bucks_tracker = BucksUtils.get_tracker_for_bucks_type(
                bucks_type, self.tracker.owner.id)
            if bucks_tracker is not None:
                bucks_tracker.try_modify_bucks(
                    bucks_type,
                    -bucks_tracker.get_bucks_amount_for_type(bucks_type))

    @classmethod
    def calculate_level_and_rank(cls, value):
        level = 0
        rank = cls.initial_rank
        for points_to_next_level in cls.get_level_list():
            value -= points_to_next_level
            if value < 0:
                break
            level += 1
            level_data = cls.event_data.get(level)
            if level_data.rank_up:
                rank += 1
        return (level, rank)

    def set_level_and_rank(self):
        (level, rank) = self.calculate_level_and_rank(self.get_value())
        self.previous_event_level = level
        self.rank_level = rank

    def should_display_delayed_decay_warning(self):
        if self.highest_level == 0:
            return False
        return super().should_display_delayed_decay_warning()

    @classproperty
    def valid_for_stat_testing(cls):
        return True

    @classmethod
    def load_statistic_data(cls, tracker, data):
        super().load_statistic_data(tracker, data)
        stat = tracker.get_statistic(cls)
        if stat is not None:
            stat._initial_loots_awarded = data.initial_loots_awarded
            stat.set_level_and_rank()
            stat.highest_level = data.highest_level
            stat.load_time_of_last_value_change(data)
            stat.fixup_callbacks_during_load()

    def save_statistic(self, commodities, skills, ranked_statistics, tracker):
        message = protocols.RankedStatistic()
        message.name_hash = self.guid64
        message.value = self.get_saved_value()
        message.highest_level = self.highest_level
        message.initial_loots_awarded = self._initial_loots_awarded
        if self._time_of_last_value_change:
            message.time_of_last_value_change = self._time_of_last_value_change.absolute_ticks(
            )
        ranked_statistics.append(message)
Example #23
0
class Season(HasTunableReference,
             metaclass=HashedTunedInstanceMetaclass,
             manager=services.get_instance_manager(
                 sims4.resources.Types.SEASON)):
    INSTANCE_TUNABLES = {
        'season_icon':
        TunableIcon(
            description="\n            The season's icon.\n            ",
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI),
        'season_name':
        TunableLocalizedString(
            description="\n            The season's name.\n            ",
            export_modes=ExportModes.All,
            tuning_group=GroupNames.UI),
        'season_length_content':
        TunableMapping(
            description=
            '\n            A mapping of season length option to the content contained within.\n            ',
            key_type=TunableEnumEntry(tunable_type=SeasonLength,
                                      default=SeasonLength.NORMAL),
            value_type=SeasonalContent.TunableFactory()),
        'screen_slam':
        OptionalTunable(
            description=
            '\n            If enabled, trigger this Screen Slam when transitioning to this season.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The screenslam to trigger, and hour of the day when it should\n                appear to the users.\n                ',
                slam=TunableScreenSlamSnippet(),
                trigger_time=TunableTimeOfDay(default_hour=6))),
        'whim_set':
        OptionalTunable(
            description=
            '\n            If enabled then this season will offer a whim set to the Sim\n            when it is that season.\n            ',
            tunable=TunableReference(
                description=
                '\n                A whim set that is active when this season is active.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ASPIRATION),
                class_restrictions=('ObjectivelessWhimSet', )))
    }

    def __init__(self, start_time, **kwargs):
        super().__init__(**kwargs)
        self._start_time = start_time
        self._length_option = None
        self._length_span = None
        self._content_data = None
        self._mid_season_begin = None
        self._absolute_mid = None
        self._late_season_begin = None
        self._end_of_season = None

    def __contains__(self, date_and_time):
        return self._start_time <= date_and_time < self._end_of_season

    @property
    def info(self):
        holiday_formatted = '\n\t'.join([
            '{} on {}'.format(holiday.__name__, day_of_season.date_and_time)
            for (holiday, day_of_season) in self.get_holiday_dates()
        ])
        return 'Resource: {}\nLength: {}\nStart: {}\n\tMid-Season Period: {}\n\tAbsolute Mid-Season: {}\n\tLate-Season Period: {}\nEnd: {}\nHolidays:\n\t{}'.format(
            self.__class__, self._length_span, self._start_time,
            self._mid_season_begin, self._absolute_mid,
            self._late_season_begin, self._end_of_season, holiday_formatted)

    @property
    def start_time(self):
        return self._start_time

    @property
    def length(self):
        return self._length_span

    @property
    def end_time(self):
        return self._end_of_season

    @property
    def midpoint_time(self):
        return self._absolute_mid

    def get_date_at_season_progress(self, progress):
        progress = clamp(0, progress, 1)
        return self._start_time + self._length_span * progress

    def get_position(self, date_and_time):
        return date_and_time - self._start_time

    def get_segment(self, date_and_time):
        if not self._verify_in_season(date_and_time):
            return
        if date_and_time < self._mid_season_begin:
            return SeasonSegment.EARLY
        if date_and_time >= self._late_season_begin:
            return SeasonSegment.LATE
        return SeasonSegment.MID

    def get_progress(self, date_and_time):
        if not self._verify_in_season(date_and_time):
            return
        current_ticks = self.get_position(date_and_time).in_ticks()
        total_ticks = self._length_span.in_ticks()
        return current_ticks / total_ticks

    def get_screen_slam_trigger_time(self):
        if self.screen_slam is None:
            return
        return self._start_time.time_of_next_day_time(
            self.screen_slam.trigger_time)

    def _verify_in_season(self, date_and_time):
        within_season = date_and_time in self
        if not within_season:
            seasons_logger.error(
                'Provided time {} is not within the current season, which is from {} to {}',
                date_and_time, self._start_time, self._end_of_season)
        return within_season

    def set_length_option(self, length_option):
        if self._length_option == length_option:
            return
        self._length_option = length_option
        self._length_span = SeasonsTuning.SEASON_LENGTH_OPTIONS[length_option](
        )
        self._calculate_important_dates()

    def _calculate_important_dates(self):
        self._content_data = self.season_length_content[self._length_option]
        self._mid_season_begin = self._start_time + self._content_data.segments.early_season_length(
        )
        self._absolute_mid = self.get_date_at_season_progress(0.5)
        self._late_season_begin = self._start_time + (
            self._length_span -
            self._content_data.segments.late_season_length())
        self._end_of_season = self._start_time + self._length_span

    def get_holiday_dates(self):
        holidays_in_season = []
        for (holiday, season_times) in self._content_data.holidays.items():
            holidays_in_season.extend(
                iter((holiday, day_of_season(self._start_time))
                     for day_of_season in season_times))
        return holidays_in_season

    def get_all_holiday_data(self):
        holidays_data = []
        for season_length in SeasonLength:
            for (
                    holiday, season_times
            ) in self.season_length_content[season_length].holidays.items():
                holidays_data.extend(
                    iter((season_length, holiday,
                          day(date_and_time.DATE_AND_TIME_ZERO).day_of_season)
                         for day in season_times))
        return holidays_data

    def get_holidays(self, season_length):
        return set(self._content_data.holidays.keys())
Example #24
0
class ObjectAnimationElement(HasTunableReference, elements.ParentElement, metaclass=TunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.ANIMATION)):
    ASM_SOURCE = 'asm_key'
    INSTANCE_TUNABLES = {ASM_SOURCE: TunableInteractionAsmResourceKey(description='\n            The ASM to use.\n            ', default=None, category='asm'), 'actor_name': Tunable(description='\n            The name of the actor in the ASM.\n            ', tunable_type=str, default=None, source_location=ASM_SOURCE, source_query=SourceQueries.ASMActorAll), 'target_name': OptionalTunable(description='\n            If enabled, some portion of this object animation expects the actor\n            to interact with another object. The object must be set by whatever\n            system uses the ASM. In and of itself, the Idle component never sets\n            this actor.\n            ', tunable=Tunable(tunable_type=str, default=None, source_location='../' + ASM_SOURCE, source_query=SourceQueries.ASMActorAll)), 'initial_state': OptionalTunable(description='\n            The name of the initial state in the ASM you expect your actor to be\n            in when running this AnimationElement. If you do not tune this we\n            will use the entry state which is usually what you want.\n            ', tunable=Tunable(tunable_type=str, default=None, source_location='../' + ASM_SOURCE, source_query=SourceQueries.ASMState), disabled_value=DEFAULT, disabled_name='use_default', enabled_name='custom_state_name'), 'begin_states': TunableList(description='\n            A list of states to play.\n            ', tunable=str, source_location=ASM_SOURCE, source_query=SourceQueries.ASMState), 'end_states': TunableList(description='\n            A list of states to play after looping.\n            ', tunable=str, source_location=ASM_SOURCE, source_query=SourceQueries.ASMState), 'balloon_data': OptionalTunable(description='\n            Information that will be used to create an element that represents\n            thought or speech balloons.\n            ', tunable=TunableTuple(icon=TunableIcon(description='\n                    Icon that will be used in the balloon. Object Animations\n                    elements only support Icon References.\n                    '), balloon_type=TunableEnumEntry(description='\n                    The visual style of the balloon background.\n                    ', tunable_type=BalloonTypeEnum, default=BalloonTypeEnum.SPEECH), overlay=OptionalTunable(description='\n                    The overlay of the balloon, if present.\n                    ', tunable=TunableIcon()), balloon_delay=Tunable(description='\n                    If set, the number of seconds after the start of the animation\n                    to trigger the balloon. A negative number will count backwards\n                    from the end of the animation.\n                    ', tunable_type=int, default=0), balloon_delay_random_offset=Tunable(description='\n                    The amount of randomization that added to balloon requests.\n                    Will always offset the delay time later and requires the delay\n                    time be set to a number. A value of 0 has no randomization.\n                    ', tunable_type=int, default=0))), 'repeat': Tunable(description='\n            If this is checked, then the begin_states will loop until the\n            controlling sequence (e.g. state change on idle component) ends. \n            At that point, end_states will play.\n            \n            This tunable allows you to create looping one-shot states. The\n            effects of this tunable on already looping states is undefined.\n            ', tunable_type=bool, default=False), 'use_surface_height': Tunable(description='\n            If checked, the asm will be provided with the surfaceHeight\n            global parameter, which uses slot height tuning to resolve the \n            height of the target object to a parameter value. \n            ', tunable_type=bool, default=False)}

    def __init__(self, owner, use_asm_cache=True, target=None, use_surface_height=False, **animate_kwargs):
        super().__init__()
        self.owner = owner
        self.target = target
        self.animate_kwargs = animate_kwargs
        self._use_asm_cache = use_asm_cache
        self._use_surface_height = use_surface_height

    @classmethod
    def append_to_arb(cls, asm, arb):
        for state_name in cls.begin_states:
            asm.request(state_name, arb)

    @classmethod
    def append_exit_to_arb(cls, asm, arb):
        for state_name in cls.end_states:
            asm.request(state_name, arb)

    def get_asm(self, use_cache=True, **kwargs):
        idle_component = self.owner.get_component(IDLE_COMPONENT)
        if idle_component is None:
            logger.error('Trying to setup an object animation {}, {} on an object {} with no Idle Component.', self, self.asm_key, self.owner)
            return
        asm = idle_component.get_asm(self.asm_key, self.actor_name, use_cache=self._use_asm_cache and use_cache, **kwargs)
        if self.target is not None:
            if self.target_name is not None:
                asm.add_potentially_virtual_actor(self.actor_name, self.owner, self.target_name, self.target)
            if self.use_surface_height:
                surface_height = get_surface_height_parameter_for_object(self.target)
                asm.set_parameter('surfaceHeight', surface_height)
        return asm

    def _run(self, timeline):
        if self.asm_key is None:
            return True
        asm = self.get_asm()
        if asm is None:
            return False
        if self.balloon_data is not None:
            sequence = self.animate_kwargs['sequence']
            if sequence is not None:
                self.animate_kwargs['sequence'] = (sequence, build_element(self.create_balloon_request(self.owner)))
            else:
                self.animation_kwargs['sequence'] = build_element(self.create_balloon_request(self.owner))
        return timeline.run_child(animate_states(asm, self.begin_states, self.end_states, repeat_begin_states=self.repeat, **self.animate_kwargs))

    def create_balloon_request(self, owner):
        (balloon_type, priority) = BALLOON_TYPE_LOOKUP[self.balloon_data.balloon_type]
        request = BalloonRequest(owner, self.balloon_data.icon, None, self.balloon_data.overlay, balloon_type, priority, TunableBalloon.BALLOON_DURATION, self.balloon_data.balloon_delay, self.balloon_data.balloon_delay_random_offset, None)
        return request
Example #25
0
class PrepTask(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'statistic': TunableReference(description='\n            Statistic that is tracked by this prep task.\n            ', manager=services.get_instance_manager(Types.STATISTIC)), 'linked_statistics': TunableList(description='\n            If specified, these are statistics whose value updates\n            are linked to the specified statistic.\n            \n            Value changes to the linked statistic are applied with\n            the tuned multiplier to the statistic.\n            ', tunable=TunableTuple(stat_type=TunableReference(manager=services.get_instance_manager(Types.STATISTIC)), multiplier=TunableRange(tunable_type=float, minimum=0.0, default=1.0))), 'task_icon': TunableIcon(description='\n            The icon to use in displaying the prep task.\n            '), 'task_description': TunableLocalizedStringFactory(description='\n            A description of the prep task. {0.String}\n            is the thresholded description.\n            '), 'task_tooltip': OptionalTunable(description='\n            If enabled, tooltip will show up on the preptask.\n            ', tunable=TunableLocalizedStringFactory(description='\n                A tooltip of the prep task. \n                ')), 'thresholded_descriptions': TunableList(description='\n            A list of thresholds and the text describing it. The\n            thresholded description will be largest threshold\n            value in this list that the commodity is >= to.\n            ', tunable=TunableTuple(threshold=Tunable(description='\n                    Threshold that the commodity must >= to.\n                    ', tunable_type=float, default=0.0), text=TunableLocalizedString(description='\n                    Description for meeting this threshold.\n                    ')))}

    def get_prep_task_progress_thresholds(self, sim_info):
        lower_threshold = None
        upper_threshold = None
        stat = sim_info.get_statistic(self.statistic)
        value = stat.get_value()
        for threshold in self.thresholded_descriptions:
            if value >= threshold.threshold:
                if lower_threshold is None or threshold.threshold > lower_threshold.threshold:
                    lower_threshold = threshold
            if value < threshold.threshold:
                if not upper_threshold is None:
                    if threshold.threshold < upper_threshold.threshold:
                        upper_threshold = threshold
                upper_threshold = threshold
        return (lower_threshold, upper_threshold)

    def get_prep_task_display_name(self, sim_info):
        loc_strings = []
        lower_threshold = None
        stat = sim_info.get_statistic(self.statistic)
        if stat is not None:
            (lower_threshold, _) = self.get_prep_task_progress_thresholds(sim_info)
        if lower_threshold:
            description = self.task_description(lower_threshold.text)
        else:
            description = self.task_description()
        loc_strings.append(description)
        if loc_strings:
            return LocalizationHelperTuning.get_new_line_separated_strings(*loc_strings)

    def is_completed(self, sim_info):
        stat = sim_info.get_statistic(self.statistic)
        if stat is None:
            return False
        return stat.get_value() >= stat.max_value
Example #26
0
class WeatherForecast(HasTunableReference,
                      metaclass=HashedTunedInstanceMetaclass,
                      manager=services.get_instance_manager(
                          Types.WEATHER_FORECAST)):
    INSTANCE_TUNABLES = {
        'calendar_icon':
        TunableIcon(
            description=
            '\n            The small icon for this forecast.\n            ',
            export_modes=ExportModes.All),
        'calendar_icon_large':
        TunableIcon(
            description=
            '\n            The large icon for this forecast.\n            ',
            export_modes=ExportModes.All),
        'calendar_icon_mascot':
        TunableIcon(
            description=
            '\n            Optional icon to use as the forecast mascot in the calendar.\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'forecast_description':
        TunableLocalizedString(
            description=
            '\n            The description for this forecast.\n            ',
            export_modes=ExportModes.All),
        'forecast_name':
        TunableLocalizedString(
            description=
            '\n            The name for this forecast.\n            ',
            export_modes=ExportModes.All),
        'prescribed_weather_type':
        OptionalTunable(
            description=
            '\n            The types of prescribed weather this forecast counts as\n            ',
            tunable=TunableTuple(
                rain=Tunable(
                    description=
                    '\n                    If checked this forecast will be unavailable if rain is disabled\n                    ',
                    tunable_type=bool,
                    default=False),
                storm=Tunable(
                    description=
                    '\n                    If checked this forecast will be unavailable if storm is disabled\n                    ',
                    tunable_type=bool,
                    default=False),
                snow=Tunable(
                    description=
                    '\n                    If checked this forecast will be unavailable if snow is disabled\n                    ',
                    tunable_type=bool,
                    default=False),
                blizzard=Tunable(
                    description=
                    '\n                    If checked this forecast will be unavailable if blizzard is disabled\n                    ',
                    tunable_type=bool,
                    default=False))),
        'weather_event_time_blocks':
        TunableMapping(
            description=
            '\n            The weather events that make up this forecast.  Key is hour of day\n            that event would start, value is a list of potential events\n            ',
            key_type=Tunable(tunable_type=int, default=0),
            value_type=TunableList(
                description=
                '\n                List of the weather events that can occur in this time block\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple of information for the weather event.\n                    ',
                    weather_event=WeatherEvent.TunableReference(
                        description=
                        '\n                        The weather event.\n                        ',
                        pack_safe=True),
                    duration=TunableInterval(
                        description=
                        '\n                        Minimum and maximum time, in sim hours, this event can last.\n                        ',
                        tunable_type=float,
                        default_lower=1,
                        default_upper=4),
                    weight=Tunable(
                        description=
                        '\n                        Weight of this event being selected.\n                        ',
                        tunable_type=int,
                        default=1)))),
        'weather_ui_override':
        TunableMapping(
            description=
            '\n            If set, this overrides the weather type that is shown for the\n            specified group.\n            ',
            key_type=TunableEnumEntry(
                tunable_type=WeatherTypeGroup,
                default=WeatherTypeGroup.UNGROUPED,
                invalid_enums=(WeatherTypeGroup.UNGROUPED, )),
            value_type=TunableEnumEntry(
                tunable_type=WeatherType,
                default=WeatherType.UNDEFINED,
                invalid_enums=(WeatherType.UNDEFINED, )),
            tuning_group=GroupNames.SPECIAL_CASES)
    }

    @classmethod
    def get_weather_event(cls):
        weather_schedule = []
        for (beginning_hour,
             event_list) in cls.weather_event_time_blocks.items():
            weather_schedule.append((beginning_hour, event_list))
        weather_schedule.sort(key=operator.itemgetter(0))
        time_of_day = services.time_service().sim_now
        hour_of_day = time_of_day.hour()
        entry = weather_schedule[-1]
        weather_events = entry[1]
        for entry in weather_schedule:
            if entry[0] <= hour_of_day:
                weather_events = entry[1]
            else:
                break
        weighted_events = [(weather_event.weight, weather_event)
                           for weather_event in weather_events]
        chosen_weather_event = random.weighted_random_item(weighted_events)
        return (chosen_weather_event.weather_event,
                chosen_weather_event.duration.random_float())

    @classmethod
    def is_snowy(cls):
        if cls.prescribed_weather_type is None:
            return False
        return cls.prescribed_weather_type.snow or cls.prescribed_weather_type.blizzard

    @classmethod
    def is_rainy(cls):
        if cls.prescribed_weather_type is None:
            return False
        return cls.prescribed_weather_type.rain or cls.prescribed_weather_type.storm

    @classmethod
    def is_forecast_supported(cls, options, snow_safe, rain_safe):
        prescribed_weather_type = cls.prescribed_weather_type
        if prescribed_weather_type is None:
            return True
        if prescribed_weather_type.rain and (
                not rain_safe or options[PrecipitationType.RAIN]
                == WeatherOption.WEATHER_DISABLED):
            return False
        if prescribed_weather_type.snow and (
                not snow_safe or options[PrecipitationType.SNOW]
                == WeatherOption.WEATHER_DISABLED):
            return False
        if prescribed_weather_type.storm and (not rain_safe or
                                              options[PrecipitationType.RAIN]
                                              == WeatherOption.DISABLE_STORMS):
            return False
        elif prescribed_weather_type.blizzard and (
                not snow_safe or options[PrecipitationType.SNOW]
                == WeatherOption.DISABLE_STORMS):
            return False
        return True