def get_cheat_responses(self, last_action_id): responses = [] total_moments = len(self._adventure_moment_keys) disabled_text = AdventureMoment.CHEAT_TEXT.tooltip curr_index = self._adventure_moment_keys.index(self._current_moment_key) responses.append(UiDialogResponse(dialog_response_id=last_action_id + AdventureMoment.CHEAT_PREVIOUS_INDEX, text=self._get_cheat_display_text(AdventureMoment.CHEAT_TEXT.previous_display_text, curr_index, total_moments), disabled_text=disabled_text() if curr_index <= 0 else None)) responses.append(UiDialogResponse(dialog_response_id=last_action_id + AdventureMoment.CHEAT_NEXT_INDEX, text=self._get_cheat_display_text(AdventureMoment.CHEAT_TEXT.next_display_text, curr_index + 2, total_moments), disabled_text=disabled_text() if curr_index >= total_moments - 1 else None)) return responses
def on_node_run(self, drama_node): resolver = drama_node._get_resolver() responses = [] for (index, possible_response) in enumerate( self.possible_responses(resolver=resolver)): responses.append( UiDialogResponse( dialog_response_id=index, text=possible_response.text, ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST)) target_sim_id = drama_node._sender_sim_info.id if drama_node._sender_sim_info is not None else None dialog = self.dialog(drama_node._receiver_sim_info, target_sim_id=target_sim_id, resolver=resolver) dialog.set_responses(responses) def response(dialog): for loot_action in self.on_dialog_complete_loot_list: loot_action.apply_to_resolver(resolver) if 0 <= dialog.response < len(self.possible_responses): for loot_action in self.possible_responses[ dialog.response].item.loot: loot_action.apply_to_resolver(resolver) DialogDramaNode.apply_cooldown_on_response(drama_node) dialog.show_dialog(on_response=response)
def create_dialog_response(button_type=ButtonType.DIALOG_RESPONSE_CLOSED, text="", ui_request=UiRequest.NO_REQUEST): return UiDialogResponse(dialog_response_id=(button_type), text=(text), ui_request=(ui_request))
def display_choices(choices, choice_callback, text: ParametrizedLocalizedString = None, title: ParametrizedLocalizedString = None, cancel: ParametrizedLocalizedString = None): try: client = services.client_manager().get_first_client() # Instantiates the dialog with the supplied title and text dlg = UiDialogChoices.TunableFactory().default( client.active_sim, text=text.localized_string, title=title.localized_string) # Creates a localized string for each choice contained within the dialog labels = [ lambda choice=choice: _create_localized_string(choice) for choice in choices ] for idx, choice in enumerate(choices): dlg._choice_responses.append( UiDialogResponse( dialog_response_id=idx, text=labels[idx], ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST)) # Standard EA cancel choice dlg._choice_responses.append( UiDialogResponse( dialog_response_id=ButtonType.DIALOG_RESPONSE_CANCEL, text=cancel.localized_string, ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST)) except Exception as e: raise e # Handles the callback response of the dialog def choice_response_callback(dialog): try: choice_callback(dialog.response) except Exception as e: raise e # Sets the dialog as the active one and displays it dlg.add_listener(choice_response_callback) dlg_service = services.ui_dialog_service() dlg_service._active_dialogs[dlg.dialog_id] = dlg dlg.show_dialog()
def responses(self): if self.bring_other_sims is not None: return ( UiDialogResponse( dialog_response_id=ButtonType.DIALOG_RESPONSE_OK, text=self.text_ok, ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST), UiDialogResponse( dialog_response_id=NPCHostedSituationDialog. BRING_OTHER_SIMS_RESPONSE_ID, text=self.bring_other_sims.text, ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST), UiDialogResponse( dialog_response_id=ButtonType.DIALOG_RESPONSE_CANCEL, text=self.text_cancel, ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST)) return super().responses
def _get_dialog(self): resolver = self.resolver dialog = self._visibility(self._sim, resolver) responses = [] for (action_id, finish_action) in enumerate(self._finish_actions): result = finish_action.availability_tests.run_tests(resolver) if not result: if finish_action.disabled_text is not None: disabled_text = finish_action.disabled_text if not result else None responses.append(UiDialogResponse(dialog_response_id=action_id, text=self._get_action_display_text(finish_action), subtext=self._interaction.create_localized_string(finish_action.display_subtext), disabled_text=disabled_text() if disabled_text is not None else None)) disabled_text = finish_action.disabled_text if not result else None responses.append(UiDialogResponse(dialog_response_id=action_id, text=self._get_action_display_text(finish_action), subtext=self._interaction.create_localized_string(finish_action.display_subtext), disabled_text=disabled_text() if disabled_text is not None else None)) if not responses: return if False and _show_all_adventure_moments: responses.extend(self._parent_adventure.get_cheat_responses(action_id)) dialog.set_responses(responses) dialog.add_listener(self._on_dialog_response) return dialog
def show_dialog(self, **kwargs): self.title = lambda **_: LocalizationHelperTuning.get_raw_text( 'Tuning Error Notifier') self.text = lambda **_: LocalizationHelperTuning.get_raw_text( 'XML tuning errors matching your filters were detected. Press the Report button below to view these in your web browser.' ) # noinspection PySetFunctionToLiteral response_command = make_immutable_slots_class( set(['arguments', 'command']))({ 'arguments': (), 'command': 'tuning_notifier.report' }) response = UiDialogResponse( dialog_response_id=ButtonType.DIALOG_RESPONSE_OK, ui_request=UiDialogResponse.UiDialogUiRequest.SEND_COMMAND, response_command=response_command, text=lambda **_: LocalizationHelperTuning.get_raw_text('Report')) self.ui_responses = (response, ) super().show_dialog(**kwargs)
class Skill(HasTunableReference, statistics.continuous_statistic_tuning.TunedContinuousStatistic, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.STATISTIC)): __qualname__ = 'Skill' SKILL_LEVEL_LIST = TunableMapping( key_type=TunableEnumEntry(SkillLevelType, SkillLevelType.MAJOR), value_type=TunableList( Tunable(int, 0), description= 'The level boundaries for skill type, specified as a delta from the previous value' ), export_modes=ExportModes.All) SKILL_EFFECTIVENESS_GAIN = TunableMapping( key_type=TunableEnumEntry(SkillEffectiveness, SkillEffectiveness.STANDARD), value_type=TunableCurve(), description='Skill gain points based on skill effectiveness.') DYNAMIC_SKILL_INTERVAL = TunableRange( description= '\n Interval used when dynamic loot is used in a\n PeriodicStatisticChangeElement.\n ', tunable_type=float, default=1, minimum=1) INSTANCE_TUNABLES = { 'stat_name': TunableLocalizedString( description= '\n Localized name of this Statistic\n ', export_modes=ExportModes.All), 'ad_data': TunableList( description= '\n A list of Vector2 points that define the desire curve for this\n commodity.\n ', tunable=TunableVector2( description= '\n Point on a Curve\n ', default=sims4.math.Vector2(0, 0))), 'weight': Tunable( description= "\n The weight of the Skill with regards to autonomy. It's ignored \n for the purposes of sorting stats, but it's applied when scoring \n the actual statistic operation for the SI.\n ", tunable_type=float, default=0.5), 'skill_level_type': TunableEnumEntry( description='\n Skill level list to use.\n ', tunable_type=SkillLevelType, default=SkillLevelType.MAJOR, export_modes=ExportModes.All), 'locked_description': TunableLocalizedString( description= "\n The skill description when it's locked.\n ", export_modes=ExportModes.All), 'skill_description': TunableLocalizedString( description= "\n The skill's normal description.\n ", export_modes=ExportModes.All), 'is_default': Tunable( description= '\n Whether Sim will default has this skill.\n ', tunable_type=bool, default=False), 'genders': TunableSet( description= '\n Skill allowed gender, empty set means not specified\n ', tunable=TunableEnumEntry(tunable_type=sim_info_types.Gender, default=None, export_modes=ExportModes.All)), 'ages': TunableSet( description= '\n Skill allowed ages, empty set means not specified\n ', tunable=TunableEnumEntry(tunable_type=sim_info_types.Age, default=None, export_modes=ExportModes.All)), 'entitlement': TunableEntitlement( description= '\n Entitlement required to use this skill.\n ' ), 'icon': TunableResourceKey( description= '\n Icon to be displayed for the Skill.\n ', default='PNG:missing_image', resource_types=sims4.resources.CompoundTypes.IMAGE, export_modes=ExportModes.All), 'tags': TunableList( description= '\n The associated categories of the skill\n ', tunable=TunableEnumEntry(tunable_type=tag.Tag, default=tag.Tag.INVALID)), 'priority': Tunable( description= '\n Skill priority. Higher priority skill will trump other skills when\n being displayed on the UI side. When a sim gains multiple skills at\n the same time only the highest priority one will display a progress\n bar over its head.\n ', tunable_type=int, default=1, export_modes=ExportModes.All), 'statistic_multipliers': TunableMapping( description= '\n Multipliers this skill applies to other statistics based on its\n value.\n ', key_type=TunableReference( description= '\n The statistic this multiplier will be applied to.\n ', manager=services.statistic_manager(), reload_dependent=True), value_type=TunableTuple( curve=TunableCurve( description= '\n Tunable curve where the X-axis defines the skill level, and\n the Y-axis defines the associated multiplier.\n ', x_axis_name='Skill Level', y_axis_name='Multiplier'), direction=TunableEnumEntry( description= "\n Direction where the multiplier should work on the\n statistic. For example, a tuned decrease for an object's\n brokenness rate will not also increase the time it takes to\n repair it.\n ", tunable_type=StatisticChangeDirection, default=StatisticChangeDirection.INCREASE), use_effective_skill=Tunable( description= '\n If checked, this modifier will look at the current\n effective skill value. If unchecked, this modifier will\n look at the actual skill value.\n ', tunable_type=bool, needs_tuning=True, default=True)), tuning_group=GroupNames.MULTIPLIERS), 'success_chance_multipliers': TunableList( description= '\n Multipliers this skill applies to the success chance of\n affordances.\n ', tunable=TunableSkillMultiplier(), tuning_group=GroupNames.MULTIPLIERS), 'monetary_payout_multipliers': TunableList( description= '\n Multipliers this skill applies to the monetary payout amount of\n affordances.\n ', tunable=TunableSkillMultiplier(), tuning_group=GroupNames.MULTIPLIERS), 'next_level_teaser': TunableList( description= '\n Tooltip which describes what the next level entails.\n ', tunable=TunableLocalizedString(), export_modes=(ExportModes.ClientBinary, )), 'level_data': TunableMapping( description= '\n Level-specific information, such as notifications to be displayed to\n level up.\n ', key_type=int, value_type=TunableTuple( level_up_notification=UiDialogNotification.TunableFactory( description= '\n The notification to display when the Sim obtains this level.\n The text will be provided two tokens: the Sim owning the\n skill and a number representing the 1-based skill level\n ', locked_args={ 'text_tokens': DEFAULT, 'icon': None, 'primary_icon_response': UiDialogResponse(text=None, ui_request=UiDialogResponse. UiDialogUiRequest.SHOW_SKILL_PANEL), 'secondary_icon': None }), level_up_screen_slam=OptionalTunable( description= '\n Screen slam to show when reaches this skill level.\n Localization Tokens: Sim - {0.SimFirstName}, Skill Name - \n {1.String}, Skill Number - {2.Number}\n ', tunable=ui.screen_slam.TunableScreenSlamSnippet(), tuning_group=GroupNames.UI))), 'mood_id': TunableReference( description= '\n When this mood is set and active sim matches mood, the UI will \n display a special effect on the skill bar to represent that this \n skill is getting a bonus because of the mood.\n ', manager=services.mood_manager(), export_modes=ExportModes.All), 'stat_asm_param': TunableStatAsmParam.TunableFactory(), 'tutorial': TunableReference( description= '\n Tutorial instance for this skill. This will be used to bring up the \n skill lesson from the first notification for Sim to know this skill.\n ', manager=services.get_instance_manager( sims4.resources.Types.TUTORIAL), class_restrictions=('Tutorial', )), 'skill_unlocks_on_max': TunableList( description= '\n A list of skills that become unlocked when this skill is maxed.\n ', tunable=TunableReference( description= '\n A skill to unlock.\n ', manager=services.get_instance_manager( sims4.resources.Types.STATISTIC), class_restrictions=('Skill', ))) } REMOVE_INSTANCE_TUNABLES = ('min_value_tuning', 'max_value_tuning', 'decay_rate', '_default_convergence_value') def __init__(self, tracker): super().__init__(tracker, self.initial_value) self._delta_enabled = True self._callback_handle = None if self.tracker.owner.is_simulating: self.on_initial_startup() self._max_level_update_sent = False def on_initial_startup(self): if self.tracker.owner.is_selectable: self.refresh_level_up_callback() def on_remove(self, on_destroy=False): super().on_remove(on_destroy=on_destroy) self._destory_callback_handle() def _apply_multipliers_to_continuous_statistics(self): for stat in self.statistic_multipliers: while stat.continuous: owner_stat = self.tracker.get_statistic(stat) if owner_stat is not None: owner_stat._recalculate_modified_decay_rate() @caches.cached def get_user_value(self): return super(Skill, self).get_user_value() def set_value(self, value, *args, from_load=False, interaction=None, **kwargs): old_value = self.get_value() super().set_value(value, *args, **kwargs) self.get_user_value.cache.clear() if not from_load: new_value = self.get_value() new_level = self.convert_to_user_value(value) if old_value == self.initial_value and old_value != new_value: sim_info = self._tracker._owner services.get_event_manager().process_event( test_events.TestEvent.SkillLevelChange, sim_info=sim_info, statistic=self.stat_type) old_level = self.convert_to_user_value(old_value) if old_level < new_level: self._apply_multipliers_to_continuous_statistics() def add_value(self, add_amount, interaction=None, **kwargs): old_value = self.get_value() if old_value == self.initial_value: telemhook = TELEMETRY_HOOK_SKILL_INTERACTION_FIRST_TIME else: telemhook = TELEMETRY_HOOK_SKILL_INTERACTION super().add_value(add_amount, interaction=interaction) self.get_user_value.cache.clear() if interaction is not None: self.on_skill_updated(telemhook, old_value, self.get_value(), interaction.affordance.__name__) def _update_value(self): old_value = self._value if gsi_handlers.sim_handlers_log.skill_change_archiver.enabled: last_update = self._last_update time_delta = super()._update_value() self.get_user_value.cache.clear() new_value = self._value if old_value == self.initial_value: telemhook = TELEMETRY_HOOK_SKILL_INTERACTION_FIRST_TIME self.on_skill_updated(telemhook, old_value, new_value, TELEMETRY_INTERACTION_NOT_AVAILABLE) sim_info = self._tracker._owner services.get_event_manager().process_event( test_events.TestEvent.SkillLevelChange, sim_info=sim_info, statistic=self.stat_type) old_level = self.convert_to_user_value(old_value) new_level = self.convert_to_user_value(new_value) if gsi_handlers.sim_handlers_log.skill_change_archiver.enabled and self.tracker.owner.is_sim: gsi_handlers.sim_handlers_log.archive_skill_change( self.tracker.owner, self, time_delta, old_value, new_value, new_level, last_update) if old_value < new_value and old_level < new_level: if self._tracker is not None: self._tracker.notify_watchers(self.stat_type, self._value, self._value) def on_skill_updated(self, telemhook, old_value, new_value, affordance_name): owner_sim = self._tracker._owner if owner_sim.is_selectable: with telemetry_helper.begin_hook(skill_telemetry_writer, telemhook, sim=owner_sim) as hook: hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64) hook.write_string(TELEMETRY_FIELD_SKILL_AFFORDANCE, affordance_name) hook.write_bool(TELEMETRY_FIELD_SKILL_AFFORDANCE_SUCCESS, True) hook.write_int(TELEMETRY_FIELD_SKILL_AFFORDANCE_VALUE_ADD, new_value - old_value) if old_value == self.initial_value: skill_level = self.convert_to_user_value(old_value) self._show_level_notification(skill_level) def _destory_callback_handle(self): if self._callback_handle is not None: self.remove_callback(self._callback_handle) self._callback_handle = None def refresh_level_up_callback(self): self._destory_callback_handle() def _on_level_up_callback(stat_inst): new_level = stat_inst.get_user_value() old_level = new_level - 1 stat_inst.on_skill_level_up(old_level, new_level) stat_inst.refresh_level_up_callback() self._callback_handle = self.add_callback( Threshold(self._get_next_level_bound(), operator.ge), _on_level_up_callback) def on_skill_level_up(self, old_level, new_level): tracker = self.tracker sim_info = tracker._owner if self.reached_max_level: for skill in self.skill_unlocks_on_max: skill_instance = tracker.add_statistic(skill, force_add=True) skill_instance.set_value(skill.initial_value) with telemetry_helper.begin_hook(skill_telemetry_writer, TELEMETRY_HOOK_SKILL_LEVEL_UP, sim=sim_info) as hook: hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64) hook.write_int(TELEMETRY_FIELD_SKILL_LEVEL, new_level) if sim_info.account is not None: services.social_service.post_skill_message(sim_info, self, old_level, new_level) self._show_level_notification(new_level) services.get_event_manager().process_event( test_events.TestEvent.SkillLevelChange, sim_info=sim_info, statistic=self.stat_type) def _show_level_notification(self, skill_level): sim_info = self._tracker._owner if not sim_info.is_npc: level_data = self.level_data.get(skill_level) if level_data is not None: tutorial_id = None if self.tutorial is not None and skill_level == 1: tutorial_id = self.tutorial.guid64 notification = level_data.level_up_notification( sim_info, resolver=SingleSimResolver(sim_info)) notification.show_dialog(icon_override=(self.icon, None), secondary_icon_override=(None, sim_info), additional_tokens=(skill_level, ), tutorial_id=tutorial_id) if level_data.level_up_screen_slam is not None: level_data.level_up_screen_slam.send_screen_slam_message( sim_info, sim_info, self.stat_name, skill_level) @classproperty def skill_type(cls): return cls @classproperty def remove_on_convergence(cls): return False @classmethod def can_add(cls, owner, force_add=False, **kwargs): if force_add: return True if cls.genders and owner.gender not in cls.genders: return False if cls.ages and owner.age not in cls.ages: return False if cls.entitlement is None: return True if owner.is_npc: return False return mtx.has_entitlement(cls.entitlement) @classmethod def get_level_list(cls): return cls.SKILL_LEVEL_LIST.get(cls.skill_level_type) @classmethod def get_max_skill_value(cls): level_list = cls.get_level_list() return sum(level_list) @classmethod def get_skill_value_for_level(cls, level): level_list = cls.get_level_list() if level > len(level_list): logger.error('Level {} out of bounds', level) return 0 return sum(level_list[:level]) @classmethod def get_skill_effectiveness_points_gain(cls, effectiveness_level, level): skill_gain_curve = cls.SKILL_EFFECTIVENESS_GAIN.get( effectiveness_level) if skill_gain_curve is not None: return skill_gain_curve.get(level) logger.error('{} does not exist in SKILL_EFFECTIVENESS_GAIN mapping', effectiveness_level) return 0 @classmethod def _tuning_loaded_callback(cls): super()._tuning_loaded_callback() level_list = cls.get_level_list() cls.max_level = len(level_list) cls.min_value_tuning = 0 cls.max_value_tuning = sum(level_list) cls._default_convergence_value = cls.min_value_tuning cls._build_utility_curve_from_tuning_data(cls.ad_data) for stat in cls.statistic_multipliers: multiplier = cls.statistic_multipliers[stat] curve = multiplier.curve direction = multiplier.direction use_effective_skill = multiplier.use_effective_skill stat.add_skill_based_statistic_multiplier(cls, curve, direction, use_effective_skill) for multiplier in cls.success_chance_multipliers: curve = multiplier.curve use_effective_skill = multiplier.use_effective_skill for affordance in multiplier.affordance_list: affordance.add_skill_multiplier( affordance.success_chance_multipliers, cls, curve, use_effective_skill) for multiplier in cls.monetary_payout_multipliers: curve = multiplier.curve use_effective_skill = multiplier.use_effective_skill for affordance in multiplier.affordance_list: affordance.add_skill_multiplier( affordance.monetary_payout_multipliers, cls, curve, use_effective_skill) @classmethod def _verify_tuning_callback(cls): success_multiplier_affordances = [] for multiplier in cls.success_chance_multipliers: success_multiplier_affordances.extend(multiplier.affordance_list) if len(success_multiplier_affordances) != len( set(success_multiplier_affordances)): logger.error( "The same affordance has been tuned more than once under {}'s success multipliers, and they will overwrite each other. Please fix in tuning.", cls, owner='tastle') monetary_payout_multiplier_affordances = [] for multiplier in cls.monetary_payout_multipliers: monetary_payout_multiplier_affordances.extend( multiplier.affordance_list) if len(monetary_payout_multiplier_affordances) != len( set(monetary_payout_multiplier_affordances)): logger.error( "The same affordance has been tuned more than once under {}'s monetary payout multipliers, and they will overwrite each other. Please fix in tuning.", cls, owner='tastle') @classmethod def convert_to_user_value(cls, value): if not cls.get_level_list(): return 0 current_value = value for (level, level_threshold) in enumerate(cls.get_level_list()): current_value -= level_threshold while current_value < 0: return level return level + 1 @classmethod def convert_from_user_value(cls, user_value): (level_min, _) = cls._get_level_bounds(user_value) return level_min @classmethod def _get_level_bounds(cls, level): level_list = cls.get_level_list() level_min = sum(level_list[:level]) if level < cls.max_level: level_max = sum(level_list[:level + 1]) else: level_max = sum(level_list) return (level_min, level_max) def _get_next_level_bound(self): level = self.convert_to_user_value(self._value) (_, level_max) = self._get_level_bounds(level) return level_max @property def reached_max_level(self): max_value = self.get_max_skill_value() if self.get_value() >= max_value: return True return False @property def should_send_update(self): if not self.reached_max_level: return True if not self._max_level_update_sent: self._max_level_update_sent = True return True return False @classproperty def is_skill(cls): return True @classproperty def autonomy_weight(cls): return cls.weight @classmethod def create_skill_update_msg(cls, sim_id, stat_value): if not cls.convert_to_user_value(stat_value) > 0: return skill_msg = Commodities_pb2.Skill_Update() skill_msg.skill_id = cls.guid64 skill_msg.curr_points = int(stat_value) skill_msg.sim_id = sim_id return skill_msg @property def is_initial_value(self): return self.initial_value == self.get_value() @classproperty def valid_for_stat_testing(cls): return True
def responses(self): return (UiDialogResponse(dialog_response_id=ButtonType.DIALOG_RESPONSE_OK, ui_request=UiDialogResponse.UiDialogUiRequest.NO_REQUEST),)
class AspirationTrack(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.ASPIRATION_TRACK)): __qualname__ = 'AspirationTrack' INSTANCE_TUNABLES = { 'display_text': sims4.localization.TunableLocalizedString( description= '\n Text used to show the Aspiration Track name in the UI', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'description_text': sims4.localization.TunableLocalizedString( description= '\n Text used to show the Aspiration Track description in the UI', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'icon': sims4.tuning.tunable.TunableResourceKey( None, resource_types=sims4.resources.CompoundTypes.IMAGE, description= '\n The icon to be displayed in the panel view.\n ', export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'icon_high_res': sims4.tuning.tunable.TunableResourceKey( None, resource_types=sims4.resources.CompoundTypes.IMAGE, description= '\n The icon to be displayed in aspiration track selection.\n ', export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'category': sims4.tuning.tunable.TunableReference( description= '\n The category this aspiration track goes into when displayed in the UI.', manager=services.get_instance_manager( sims4.resources.Types.ASPIRATION_CATEGORY), export_modes=sims4.tuning.tunable_base.ExportModes.All), 'primary_trait': sims4.tuning.tunable.TunableReference( description= '\n This is the primary Aspiration reward trait that is applied upon selection from CAS.', manager=services.get_instance_manager(sims4.resources.Types.TRAIT), export_modes=sims4.tuning.tunable_base.ExportModes.All), 'aspirations': sims4.tuning.tunable.TunableMapping( description= '\n A Set of objectives for completing an aspiration.', key_type=TunableEnumEntry(AspirationTrackLevels, AspirationTrackLevels.LEVEL_1), value_type=sims4.tuning.tunable.TunableReference( description= '\n One aspiration in the track, associated for a level', manager=services.get_instance_manager( sims4.resources.Types.ASPIRATION), class_restrictions='Aspiration', reload_dependent=True), export_modes=sims4.tuning.tunable_base.ExportModes.All), 'reward': sims4.tuning.tunable.TunableReference( description= '\n Which rewards are given when this aspiration track is completed.', manager=services.get_instance_manager( sims4.resources.Types.REWARD), export_modes=sims4.tuning.tunable_base.ExportModes.All), 'notification': UiDialogNotification.TunableFactory( description= '\n This text will display in a notification pop up when completed.\n ', locked_args={ 'text_tokens': DEFAULT, 'icon': None, 'primary_icon_response': UiDialogResponse(text=None, ui_request=UiDialogResponse.UiDialogUiRequest. SHOW_ASPIRATION_SELECTOR), 'secondary_icon': None }), 'mood_asm_param': sims4.tuning.tunable.Tunable( description= "\n The asm parameter for Sim's mood for use with CAS ASM state machine, driven by selection\n of this AspirationTrack, i.e. when a player selects the a romantic aspiration track, the Flirty\n ASM is given to the state machine to play. The name tuned here must match the animation\n state name parameter expected in Swing.", tunable_type=str, default=None, source_query=SourceQueries.SwingEnumNamePattern.format('mood'), export_modes=sims4.tuning.tunable_base.ExportModes.All) } _sorted_aspirations = None @classmethod def get_aspirations(cls): return cls._sorted_aspirations @classmethod def get_next_aspriation(cls, current_aspiration): next_aspiration_level = None current_aspiration_guid = current_aspiration.guid64 for (level, track_aspiration) in cls.aspirations.items(): while track_aspiration.guid64 == current_aspiration_guid: next_aspiration_level = int(level) + 1 break if next_aspiration_level in cls.aspirations: return cls.aspirations[next_aspiration_level] @classproperty def is_child_aspiration_track(cls): return cls._sorted_aspirations[0][1].is_child_aspiration @classmethod def _tuning_loaded_callback(cls): cls._sorted_aspirations = tuple(sorted(cls.aspirations.items())) @classmethod def _verify_tuning_callback(cls): logger.debug('Loading asset: {}', cls, owner='ddriscoll') if cls.category == None: logger.error('{} Aspiration Track has no category set.', cls, owner='ddriscoll') if len(cls.aspirations) == 0: logger.error( '{} Aspiration Track has no aspirations mapped to levels.', cls, owner='ddriscoll') else: aspiration_list = cls.aspirations.values() aspiration_set = set(aspiration_list) if len(aspiration_set) != len(aspiration_list): logger.error( '{} Aspiration Track has repeating aspiration values in the aspiration map.', cls, owner='ddriscoll')
class 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 Achievement(Milestone, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.ACHIEVEMENT)): INSTANCE_TUNABLES = { 'display_name': sims4.localization.TunableLocalizedString( description='\n Name of this Achievement.\n ', export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'descriptive_text': sims4.localization.TunableLocalizedString( description= '\n Description of this Achievement.\n ', export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'point_value': sims4.tuning.tunable.Tunable( description= '\n Point value for an achievement.\n ', tunable_type=int, default=1, export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.REWARDS), 'pid': sims4.tuning.tunable.TunableRange( description='\n PID for an achievement.\n ', tunable_type=int, default=0, minimum=0, maximum=127, export_modes=sims4.tuning.tunable_base.ExportModes.ClientBinary, tuning_group=GroupNames.UI), 'xid': sims4.tuning.tunable.Tunable( description='\n XID for an achievement.\n ', tunable_type=str, default='', allow_empty=True, export_modes=sims4.tuning.tunable_base.ExportModes.ClientBinary, tuning_group=GroupNames.UI), 'reward': sims4.tuning.tunable.TunableReference( description= '\n The reward received when this achievement is completed.\n ', manager=services.get_instance_manager( sims4.resources.Types.REWARD), allow_none=True, export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.REWARDS), 'category': sims4.tuning.tunable.TunableList( description= '\n A List of all of the categories that this Achievement is a part of.\n ', tunable=sims4.tuning.tunable.TunableReference( description= '\n One of the categories that this Achievement is a part of.\n ', manager=services.get_instance_manager( sims4.resources.Types.ACHIEVEMENT_CATEGORY)), export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'is_hidden': sims4.tuning.tunable.Tunable( description= '\n If checked then this Achievement will be hidden from the\n Achievement UI until it has been completed.\n ', tunable_type=bool, default=False, export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'icon': sims4.tuning.tunable.TunableResourceKey( None, resource_types=sims4.resources.CompoundTypes.IMAGE, description= '\n The icon to be displayed in the panel view.\n ', export_modes=sims4.tuning.tunable_base.ExportModes.All, tuning_group=GroupNames.UI), 'screen_slam': OptionalTunable( description= '\n Which screen slam to show when this achievement is completed. \n Localization Tokens: Achievement Name = {0.String}\n ', tunable=ui.screen_slam.TunableScreenSlamSnippet(), tuning_group=GroupNames.UI), 'notification': OptionalTunable( description= '\n If enabled, this notification will show when the achievement is\n completed.\n ', tunable=UiDialogNotification.TunableFactory( locked_args={ 'title': None, 'text': None, 'icon': None, 'primary_icon_response': UiDialogResponse(text=None, ui_request=UiDialogResponse. UiDialogUiRequest.SHOW_ACHIEVEMENTS) }), tuning_group=GroupNames.UI) } @classmethod def handle_event(cls, sim_info, event, resolver): if sim_info is not None and sim_info.account is not None: sim_info.account.achievement_tracker.handle_event( cls, event, resolver) @classmethod def register_callbacks(cls): tests = [objective.objective_test for objective in cls.objectives] services.get_event_manager().register_tests(cls, tests) @classmethod def show_achievement_notification(cls, sim_info): if cls.notification is not None: dialog = cls.notification( sim_info, SingleSimResolver(sim_info), title=lambda *_, **__: cls.display_name, text=lambda *_, **__: cls.descriptive_text) dialog.show_dialog( icon_override=IconInfoData(icon_resource=cls.icon), event_id=cls.guid64)
def _get_dialog(self): dialog = self._visibility(self._sim, self._interaction.get_resolver()) dialog.set_responses(tuple(UiDialogResponse(dialog_response_id=i, text=self._get_action_display_text(action)) for (i, action) in enumerate(self._finish_actions))) dialog.add_listener(self._on_dialog_response) return dialog
class Achievement(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.ACHIEVEMENT)): __qualname__ = 'Achievement' INSTANCE_TUNABLES = { 'objectives': sims4.tuning.tunable.TunableList( sims4.tuning.tunable.TunableReference( manager=services.get_instance_manager( sims4.resources.Types.OBJECTIVE), description='One objective for an achievement'), description='A Set of objectives for completing an achievement.', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'display_name': sims4.localization.TunableLocalizedString( description='Display name for this achievement', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'descriptive_text': sims4.localization.TunableLocalizedString( description='Description for this achievement', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'point_value': sims4.tuning.tunable.Tunable( int, 1, description='Point value for an achievement.', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'reward': sims4.tuning.tunable.TunableReference( manager=services.get_instance_manager( sims4.resources.Types.REWARD), description= 'Which rewards are given when this achievement is completed.', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'category': sims4.tuning.tunable.TunableList( sims4.tuning.tunable.TunableReference( manager=services.get_instance_manager( sims4.resources.Types.ACHIEVEMENT_CATEGORY), description= 'One of the categories associated with this achievement.'), export_modes=sims4.tuning.tunable_base.ExportModes.All), 'is_origin': sims4.tuning.tunable.Tunable( bool, False, description='This is an Origin Achievement.', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'is_hidden': sims4.tuning.tunable.Tunable( bool, False, needs_tuning=True, description= 'This Achievement is hidden from the player until achieved.', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'valid_with_cheats': sims4.tuning.tunable.Tunable( bool, False, needs_tuning=True, description= 'This Achievement is still attainable even for players using of cheats.', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'disabled': sims4.tuning.tunable.Tunable( description= '\n Checking this box will remove this Achievement from the event system and the UI, but preserve the tuning.', tunable_type=bool, default=False, export_modes=sims4.tuning.tunable_base.ExportModes.All), 'icon': sims4.tuning.tunable.TunableResourceKey( None, resource_types=sims4.resources.CompoundTypes.IMAGE, description= '\n The icon to be displayed in the panel view.\n ', export_modes=sims4.tuning.tunable_base.ExportModes.All), 'screen_slam': OptionalTunable( description= '\n Which screen slam to show when this achievement is completed. \n Localization Tokens: Achievement Name = {0.String}\n ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'notification': OptionalTunable( description= '\n If enabled, this notification will show when the achievement is\n completed.\n ', tunable=UiDialogNotification.TunableFactory( locked_args={ 'title': None, 'text': None, 'icon': None, 'primary_icon_response': UiDialogResponse(text=None, ui_request=UiDialogResponse. UiDialogUiRequest.SHOW_ACHIEVEMENTS) })) } @classmethod def handle_event(cls, sim_info, event, resolver): if sim_info is not None and sim_info.account is not None: sim_info.account.achievement_tracker.handle_event( cls, event, resolver) @classmethod def register_callbacks(cls): tests = [objective.objective_test for objective in cls.objectives] services.get_event_manager().register_tests(cls, tests) @classmethod def show_achievement_notification(cls, sim_info): if cls.notification is not None: dialog = cls.notification( sim_info, SingleSimResolver(sim_info), title=lambda *_, **__: cls.display_name, text=lambda *_, **__: cls.descriptive_text) dialog.show_dialog(icon_override=(cls.icon, None), event_id=cls.guid64)