class VetClinicTuning: UNIFORM_EMPLOYEE_MALE = TunablePackSafeResourceKey( description= '\n The SimInfo file to use to edit male employee uniforms.\n ', default=None, resource_types=(sims4.resources.Types.SIMINFO, ), export_modes=ExportModes.All) UNIFORM_EMPLOYEE_FEMALE = TunablePackSafeResourceKey( description= '\n The SimInfo file to use to edit female employee uniforms.\n ', default=None, resource_types=(sims4.resources.Types.SIMINFO, ), export_modes=ExportModes.All) VET_CLINIC_VENUE = TunablePackSafeReference( description= '\n This is a tunable reference to the type of this Venue.\n ', manager=services.get_instance_manager(sims4.resources.Types.VENUE)) DEFAULT_PROFIT_PER_TREATMENT_FOR_OFF_LOT_SIMULATION = TunableRange( description= '\n This is used as the default profit for a treatment for off-lot simulation.\n Once enough actual treatments have been performed, this value becomes \n irrelevant and the MAX_COUNT_FOR_OFF_LOT_PROFIT_PER_TREATMENT tunable comes into use. \n ', tunable_type=int, default=20, minimum=1) MAX_COUNT_FOR_OFF_LOT_PROFIT_PER_TREATMENT = TunableRange( description= '\n The number of treatments to keep a running average of for the profit\n per treatment calculations during off lot simulations.\n ', tunable_type=int, default=10, minimum=2) VET_SKILL = Skill.TunablePackSafeReference( description= '\n The vet skill for reference in code. This can resolve to None\n if the pack providing the skill is not installed, so beware.\n ' ) VALUE_OF_SERVICE_AWARDS = TunableList( description= '\n A threshold matrix that maps buffs to level of markup and vet skill.\n\n Order is important. The list is processed in reverse order.\n The first threshold that passes returns the amount associated with it.\n Because of this, optimal order is thresholds is ordered from lesser \n to greater threshold values.\n ', tunable=TunableTuple( description= '\n A pair of markup threshold and skill threshold-to-buff list.\n ', markup_threshold=TunableThreshold( description='The threshold at which this item will match.'), skill_to_buffs=TunableList( description= '\n Mapping of skill threshold to the value of service that is applied.\n \n Order is important. The list is processed in reverse order.\n The first threshold that passes returns the amount associated with it.\n Because of this, optimal order is thresholds is ordered from lesser \n to greater threshold values.\n ', tunable=TunableTuple( description= "\n A pair of skill threshold to the buff that will apply\n if this threshold is met when the patient is billed\n for a vet's services.\n ", skill_range=SkillRangeTest.TunableFactory( skill_range=SkillThreshold.TunableFactory(), callback=set_vet_skill_on_threshold_test, locked_args={ 'subject': ParticipantType.Actor, 'skill': None, 'tooltip': None }), value_of_service_buff=TunableReference( manager=services.get_instance_manager(Types.BUFF), pack_safe=True)))), verify_tunable_callback=verify_value_of_service) DIFFICULTY_BONUS_PAYMENT = TunableList( description= '\n When an NPC or player Sim treats an NPC Sim, they can get a difficulty\n bonus depending on the difficulty of the sickness (if it is the correct\n and ideal treatment for the sickness).\n \n Order is important. The list is processed in reverse order.\n The first threshold that passes returns the amount associated with it.\n Because of this, optimal order is thresholds is ordered from lesser \n to greater threshold values.\n \n If no thresholds pass, returned bonus amount is 0.\n ', tunable=TunableTuple( description= '\n A pair of payment amount and threshold that the payment applies to.\n ', bonus_amount=TunableRange(tunable_type=int, default=100, minimum=0), threshold=TunableThreshold()), verify_tunable_callback=verify_difficulty_bonuses)
class SkillBasedMultiInteraction(SocialSuperInteraction): INSTANCE_TUNABLES = { 'skill_interactions': TunableList( description= '\n For each item in this list, if the actor has the skill, and the\n object criteria test passes, a new interaction will be generated on\n the Sim.\n ', tunable=TunableTuple( description= '\n If the actor has the skill, and the object criteria test passes,\n a new interaction will be generated on the Sim.\n ', skill=Skill.TunablePackSafeReference( description= '\n If the actor of the interaction has this skill, the Interaction Data will be\n used to generate interactions on the actor.\n ' ), object_criteria=OptionalTunable( ObjectCriteriaAndSpecificTests.TunableFactory( description= '\n If enabled, the object criteria test must also pass for the\n Interaction Data to generate interactions on the actor. \n ' )), interaction_data=TunableList( TunableTuple( description= '\n The data used to generate interactions on the actor.,\n ', interaction_name=TunableLocalizedStringFactory( description= '\n The name given to the generated interaction.\n ' ), outcome=TunableOutcome( description= '\n The outcome to use for the generated interaction.\n ' )))), tuning_group=GroupNames.CORE) } def __init__(self, *args, display_name_override=None, outcome_override=None, **kwargs): super().__init__(*args, **kwargs) self._display_name_override = display_name_override self._outcome_override = outcome_override @classmethod def potential_interactions(cls, target, context, **kwargs): resolver = SingleSimResolver(context.sim) for skill_interaction in cls.skill_interactions: skill_tracker = target.get_tracker(skill_interaction.skill) if skill_tracker is None: continue skill_stat = skill_tracker.get_statistic(skill_interaction.skill, add=False) if skill_stat is None: continue if not not skill_interaction.object_criteria is not None and not not resolver( skill_interaction.object_criteria): continue for interaction_datum in skill_interaction.interaction_data: yield AffordanceObjectPair( cls, target, cls, None, display_name_override=interaction_datum.interaction_name, outcome_override=interaction_datum.outcome, **kwargs) def _build_outcome_sequence(self): if self._outcome_override is not None: return self._outcome_override.build_elements( self, update_global_outcome_result=True) @flexmethod def _get_name(cls, inst, target=DEFAULT, context=DEFAULT, display_name_override=None, **interaction_parameters): if display_name_override is not None: return display_name_override() return super(SuperInteraction, inst if inst is not None else cls)._get_name( target=target, context=context, **interaction_parameters)
class Photography: SMALL_PORTRAIT_OBJ_DEF = TunablePackSafeReference( description= '\n Object definition for a small portrait photo.\n ', manager=services.definition_manager()) SMALL_LANDSCAPE_OBJ_DEF = TunablePackSafeReference( description= '\n Object definition for a small landscape photo.\n ', manager=services.definition_manager()) MEDIUM_PORTRAIT_OBJ_DEF = TunablePackSafeReference( description= '\n Object definition for a medium portrait photo.\n ', manager=services.definition_manager()) MEDIUM_LANDSCAPE_OBJ_DEF = TunablePackSafeReference( description= '\n Object definition for a medium landscape photo.\n ', manager=services.definition_manager()) LARGE_PORTRAIT_OBJ_DEF = TunablePackSafeReference( description= '\n Object definition for a large portrait photo.\n ', manager=services.definition_manager()) LARGE_LANDSCAPE_OBJ_DEF = TunablePackSafeReference( description= '\n Object definition for a large landscape photo.\n ', manager=services.definition_manager()) PAINTING_INTERACTION_TAG = TunableEnumEntry( description= '\n Tag to specify a painting interaction.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID) PHOTOGRAPHY_LOOT_LIST = TunableList( description= '\n A list of loot operations to apply to the photographer when photo mode exits.\n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.ACTION), class_restrictions=('LootActions', ), pack_safe=True)) FAIL_PHOTO_QUALITY_RANGE = TunableInterval( description= '\n The random quality statistic value that a failure photo will be\n given between the min and max tuned values.\n ', tunable_type=int, default_lower=0, default_upper=100) BASE_PHOTO_QUALITY_MAP = TunableMapping( description= '\n The mapping of CameraQuality value to an interval of quality values\n that will be used to asign a random base quality value to a photo\n as it is created.\n ', key_type=TunableEnumEntry( description= '\n The CameraQuality value. If this photo has this CameraQuality,\n value, then a random quality between the min value and max value\n will be assigned to the photo.\n ', tunable_type=CameraQuality, default=CameraQuality.CHEAP), value_type=TunableInterval( description= '\n The range of base quality values from which a random value will be\n given to the photo.\n ', tunable_type=int, default_lower=1, default_upper=100)) QUALITY_MODIFIER_PER_SKILL_LEVEL = Tunable( description= '\n For each level of skill in Photography, this amount will be added to\n the quality statistic.\n ', tunable_type=float, default=0) PHOTO_VALUE_MODIFIER_MAP = TunableMapping( description= '\n The mapping of state values to Simoleon value modifiers.\n The final value of a photo is decided based on its\n current value multiplied by the sum of all modifiers for\n states that apply to the photo. All modifiers are\n added together first, then the sum will be multiplied by\n the current price.\n ', key_type=TunableStateValueReference( description= '\n The quality state values. If this photo has this state,\n then a random modifier between min_value and max_value\n will be multiplied to the current price.' ), value_type=TunableInterval( description= '\n The maximum modifier multiplied to the current price based on the provided state value\n ', tunable_type=float, default_lower=1, default_upper=1)) PHOTO_VALUE_SKILL_CURVE = TunableStatisticModifierCurve.TunableFactory( description= "\n Allows you to adjust the final value of the photo based on the Sim's\n level of a given skill.\n ", axis_name_overrides=('Skill Level', 'Simoleon Multiplier'), locked_args={'subject': ParticipantType.Actor}) PHOTOGRAPHY_SKILL = Skill.TunablePackSafeReference( description='\n A reference to the photography skill.\n ' ) EMOTION_STATE_MAP = TunableMapping( description= "\n The mapping of moods to states, used to give photo objects a mood\n based state. These states are then used by the tooltip component to\n display emotional content on the photo's tooltip.\n ", key_type=TunableReference( description= '\n The mood to associate with a state.\n ', manager=services.mood_manager()), value_type=TunableStateValueReference( description= '\n The state that represents the mood for the purpose of displaying\n emotional content in a tooltip.\n ' )) PHOTO_OBJECT_LOOT_PER_TARGET = TunableList( description= '\n A list of loots which will be applied once PER target. The participants\n for each application will be Actor: photographer, Target: photograph\n target and Object: the Photograph itself. If a photo interaction has 2\n target sims, this loot will be applied twice.\n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.ACTION), pack_safe=True)) MOOD_PARAM_TO_MOOD_CATEGORY_STATE = TunableMapping( description= '\n If the player took a picture in a photo mode that supports mood\n categories, we will perform a state change to the corresponding state\n based on the mood that each picture was taken in.\n ', key_type=Tunable( description= '\n The mood ASM parameter value to associate with a state.\n ', tunable_type=str, default=None), value_type=TunableStateValueReference( description= '\n The state that represents the mood category.\n ' )) GROUP_PHOTO_X_ACTOR_TAG = TunableEnumEntry( description= '\n Tag to specify the photo studio interaction that the photo target sim\n who should be considered the x actor will run.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID, )) GROUP_PHOTO_Y_ACTOR_TAG = TunableEnumEntry( description= '\n Tag to specify the photo studio interaction that the photo target sim\n who should be considered the y actor will run.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID, )) GROUP_PHOTO_Z_ACTOR_TAG = TunableEnumEntry( description= '\n Tag to specify the photo studio interaction that the photo target sim\n who should be considered the z actor will run.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID, )) NUM_PHOTOS_PER_SESSION = Tunable( description= '\n Max possible photos that can be taken during one photo session. Once\n this number has been reached, the photo session will exit.\n ', tunable_type=int, default=5) @classmethod def _is_fail_photo(cls, photo_style_type): if photo_style_type == PhotoStyleType.EFFECT_GRAINY or ( photo_style_type == PhotoStyleType.EFFECT_OVERSATURATED or (photo_style_type == PhotoStyleType.EFFECT_UNDERSATURATED or (photo_style_type == PhotoStyleType.PHOTO_FAIL_BLURRY or (photo_style_type == PhotoStyleType.PHOTO_FAIL_FINGER or photo_style_type == PhotoStyleType.PHOTO_FAIL_GNOME))) ) or photo_style_type == PhotoStyleType.PHOTO_FAIL_NOISE: return True return False @classmethod def _apply_quality_and_value_to_photo(cls, photographer_sim, photo_obj, photo_style, camera_quality): quality_stat = CraftingTuning.QUALITY_STATISTIC quality_stat_tracker = photo_obj.get_tracker(quality_stat) if cls._is_fail_photo(photo_style): final_quality = cls.FAIL_PHOTO_QUALITY_RANGE.random_int() else: quality_range = cls.BASE_PHOTO_QUALITY_MAP.get( camera_quality, None) if quality_range is None: logger.error( 'Photography tuning BASE_PHOTO_QUALITY_MAP does not have an expected quality value: []', str(camera_quality)) return base_quality = quality_range.random_int() skill_quality_modifier = 0 if cls.PHOTOGRAPHY_SKILL is not None: effective_skill_level = photographer_sim.get_effective_skill_level( cls.PHOTOGRAPHY_SKILL) if effective_skill_level: skill_quality_modifier = effective_skill_level * cls.QUALITY_MODIFIER_PER_SKILL_LEVEL final_quality = base_quality + skill_quality_modifier quality_stat_tracker.set_value(quality_stat, final_quality) value_multiplier = 1 for (state_value, value_mods) in cls.PHOTO_VALUE_MODIFIER_MAP.items(): if photo_obj.has_state(state_value.state): actual_state_value = photo_obj.get_state(state_value.state) if state_value is actual_state_value: value_multiplier *= value_mods.random_float() break value_multiplier *= cls.PHOTO_VALUE_SKILL_CURVE.get_multiplier( SingleSimResolver(photographer_sim), photographer_sim) photo_obj.base_value = int(photo_obj.base_value * value_multiplier) @classmethod def _get_mood_sim_info_if_exists(cls, photographer_sim_info, target_sim_ids, camera_mode): if camera_mode is CameraMode.SELFIE_PHOTO: return photographer_sim_info else: num_target_sims = len(target_sim_ids) if num_target_sims == 1: sim_info_manager = services.sim_info_manager() target_sim_info = sim_info_manager.get(target_sim_ids[0]) return target_sim_info @classmethod def _apply_mood_state_if_appropriate(cls, photographer_sim_info, target_sim_ids, camera_mode, photo_object): mood_sim_info = cls._get_mood_sim_info_if_exists( photographer_sim_info, target_sim_ids, camera_mode) if mood_sim_info: mood = mood_sim_info.get_mood() mood_state = cls.EMOTION_STATE_MAP.get(mood, None) if mood_state: photo_object.set_state(mood_state.state, mood_state) @classmethod def _apply_mood_category_state_if_appropriate(cls, selected_mood_param, camera_mode, photo_object): if camera_mode in (CameraMode.TRIPOD, CameraMode.SIM_PHOTO, CameraMode.PHOTO_STUDIO_PHOTO): mood_category_state = cls.MOOD_PARAM_TO_MOOD_CATEGORY_STATE.get( selected_mood_param, None) if mood_category_state: photo_object.set_state(mood_category_state.state, mood_category_state) @classmethod def create_photo_from_photo_data(cls, camera_mode, camera_quality, photographer_sim_id, target_obj_id, target_sim_ids, res_key, photo_style, photo_size, photo_orientation, photographer_sim_info, photographer_sim, time_stamp, selected_mood_param): photo_object = None is_paint_by_reference = camera_mode is CameraMode.PAINT_BY_REFERENCE if is_paint_by_reference: current_zone = services.current_zone() photo_object = current_zone.object_manager.get(target_obj_id) if photo_object is None: photo_object = current_zone.inventory_manager.get( target_obj_id) else: if photo_orientation == PhotoOrientation.LANDSCAPE: if photo_size == PhotoSize.LARGE: photo_object_def = cls.LARGE_LANDSCAPE_OBJ_DEF elif photo_size == PhotoSize.MEDIUM: photo_object_def = cls.MEDIUM_LANDSCAPE_OBJ_DEF elif photo_size == PhotoSize.SMALL: photo_object_def = cls.SMALL_LANDSCAPE_OBJ_DEF elif photo_orientation == PhotoOrientation.PORTRAIT: if photo_size == PhotoSize.LARGE: photo_object_def = cls.LARGE_PORTRAIT_OBJ_DEF elif photo_size == PhotoSize.MEDIUM: photo_object_def = cls.MEDIUM_PORTRAIT_OBJ_DEF elif photo_size == PhotoSize.SMALL: photo_object_def = cls.SMALL_PORTRAIT_OBJ_DEF else: photo_object_def = cls.SMALL_LANDSCAPE_OBJ_DEF if photo_object_def is None: return photo_object = create_object(photo_object_def) if photo_object is None: logger.error('photo object could not be found.') return for target_sim_id in target_sim_ids: target_sim_info = services.sim_info_manager().get(target_sim_id) target_sim = target_sim_info.get_sim_instance() resolver = DoubleSimAndObjectResolver(photographer_sim, target_sim, photo_object, source=cls) for loot in cls.PHOTO_OBJECT_LOOT_PER_TARGET: loot.apply_to_resolver(resolver) photography_service = services.get_photography_service() loots = photography_service.get_loots_for_photo() for photoloot in loots: if photoloot._AUTO_FACTORY.FACTORY_TYPE is RotateTargetPhotoLoot: photographer_sim = photoloot.photographer photographer_sim_info = photographer_sim.sim_info break reveal_level = PaintingState.REVEAL_LEVEL_MIN if is_paint_by_reference else PaintingState.REVEAL_LEVEL_MAX painting_state = PaintingState.from_key(res_key, reveal_level, False, photo_style) photo_object.canvas_component.painting_state = painting_state photo_object.canvas_component.time_stamp = time_stamp photo_object.set_household_owner_id(photographer_sim.household_id) if selected_mood_param: cls._apply_mood_category_state_if_appropriate( selected_mood_param, camera_mode, photo_object) if not is_paint_by_reference: cls._apply_quality_and_value_to_photo(photographer_sim, photo_object, photo_style, camera_quality) cls._apply_mood_state_if_appropriate(photographer_sim_info, target_sim_ids, camera_mode, photo_object) photo_object.add_dynamic_component(STORED_SIM_INFO_COMPONENT, sim_id=photographer_sim.id) photo_object.update_object_tooltip() if not (photographer_sim.inventory_component.can_add(photo_object) and photographer_sim.inventory_component. player_try_add_object(photo_object)): logger.error( "photo object could not be put in the sim's inventory, deleting photo." ) photo_object.destroy() photo_targets = [ services.sim_info_manager().get(sim_id) for sim_id in target_sim_ids ] if camera_mode == CameraMode.TWO_SIM_SELFIE_PHOTO: photo_targets.append(photographer_sim_info) photo_targets = frozenset(photo_targets) services.get_event_manager().process_event( test_events.TestEvent.PhotoTaken, sim_info=photographer_sim_info, photo_object=photo_object, photo_targets=photo_targets)
class SkillRangeTest(HasTunableSingletonFactory, AutoFactoryInit, event_testing.test_base.BaseTest): FACTORY_TUNABLES = { 'subject': TunableEnumEntry( description='\n The subject of this test.\n ', tunable_type=ParticipantType, default=ParticipantType.Actor), 'skill': Skill.TunablePackSafeReference( description= '\n The skill to test against. \n \n Should the Sim not have the specified skill, or should the skill not\n be available because of pack restrictions, this Sim will be\n considered at level 0.\n ' ), 'skill_range': TunableVariant( description= '\n A skill range defined by either an interval or a threshold.\n ', interval=SkillInterval.TunableFactory(), threshold=SkillThreshold.TunableFactory(), default='interval'), 'use_effective_skill_level': Tunable( description= "\n If checked, then instead of using the skill's actual level, the test\n will use the skill's effective level for the purpose of satisfying\n the specified criteria.\n ", tunable_type=bool, needs_tuning=True, default=False) } __slots__ = ('subject', 'skill', 'skill_range', 'use_effective_skill_level') def get_expected_args(self): return {'test_targets': self.subject} @property def skill_range_min(self): return self.skill_range.skill_range_min @property def skill_range_max(self): max_possible_level = self.skill.get_max_skill_value() range_max = self.skill_range.skill_range_max if range_max > max_possible_level: logger.error( "SkillRangeTest has a tuned skill range upper bound of {} that is higher than {}'s highest level of {}.", self.skill, range_max, max_possible_level, owner='rmccord') return range_max @cached_test def __call__(self, test_targets=()): for target in test_targets: if self.skill is None: skill_value = 0 else: skill_or_skill_type = target.get_statistic( self.skill, add=False) or self.skill if self.use_effective_skill_level and target.is_instanced(): skill_value = target.get_sim_instance( allow_hidden_flags=ALL_HIDDEN_REASONS ).get_effective_skill_level(skill_or_skill_type) else: skill_value = skill_or_skill_type.get_user_value() if not self.skill_range(skill_value): return TestResult(False, 'skill level not in desired range.', tooltip=self.tooltip) return TestResult.TRUE return TestResult(False, 'Sim does not have required skill.', tooltip=self.tooltip)