class EnvironmentScoreTuning: ENVIRONMENT_SCORE_BROADCASTER = BroadcasterEnvironmentScore.TunableReference(description='\n The singleton broadcaster that groups all scoring objects. The\n constraints on this broadcaster determine the constraint within which a\n Sim is affected by environment score.\n ') ENVIRONMENT_SCORE_MOODS = TunableMapping(description="\n Tags on Objects correspond to a particular Mood.\n \n When an object is going to contribute to the environment score, put a\n tag in it's catalog object, and make sure that tag points to a Mood\n here.\n ", key_type=TunableEnumEntry(description='\n The Tag that corresponds to mood and environmental scoring data.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID), value_type=Mood.TunableReference(description='\n The mood that the Sim must be in for an object that emits this mood\n to score. Corresponds to the mood_tag.\n '), key_name='object_tag', value_name='mood') NEGATIVE_ENVIRONMENT_SCORING = Commodity.TunableReference(description='\n Defines the ranges and corresponding buffs to apply for negative\n environmental contribution.\n \n Be sure to tune min, max, and the different states. The convergence\n value is what will remove the buff. Suggested to be 0.\n ') POSITIVE_ENVIRONMENT_SCORING = Commodity.TunableReference(description='\n Defines the ranges and corresponding buffs to apply for positive\n environmental contribution.\n \n Be sure to tune min, max, and the different states. The convergence\n value is what will remove the buff. Suggested to be 0.\n ') BUILD_OBJECTS_ENVIRONMENT_SCORING = TunableTuple(description='\n Defines the statistics which track the value of positive and negative\n environmental contribution from build objects.\n ', negative_environment_scoring=TunableReference(description='\n Negative environmental statistic.\n ', manager=services.get_instance_manager(Types.STATISTIC), class_restrictions=('Statistic',)), positive_environment_scoring=TunableReference(description='\n Positive environmental statistic.\n ', manager=services.get_instance_manager(Types.STATISTIC), class_restrictions=('Statistic',))) ENABLE_AFFORDANCE = TunableReference(description='\n The interaction that will turn on Environment Score for a particular\n object. This interaction should set a state on the object that will\n have multipliers of 1 and adders of 0 for all moods.\n ', manager=services.affordance_manager()) DISABLE_AFFORDANCE = TunableReference(description='\n The interaction that will turn off Environment Score for a particular\n object. This interaction should set a state on the object that will\n have multipliers of 0 and adders of 0 for all moods.\n ', manager=services.affordance_manager()) ENABLED_STATE_VALUE = ObjectStateValue.TunableReference(description='\n A state value that indicates the object should be contributing\n Environment Score.\n ') DISABLED_STATE_VALUE = ObjectStateValue.TunableReference(description='\n A state value that indicates the object should not be contributing\n Environment Score.\n ')
class NewObjectTuning: NEW_OBJECT_COMMODITY = Commodity.TunableReference() NEW_OBJECT_AFFORDANCES = TunableSet( description= '\n Affordances available on an object as long as its considered as new.\n ', tunable=SuperInteraction.TunableReference( description= '\n Affordance reference to add to new objects.\n ', pack_safe=True))
class WaitAroundState(CommonSituationState): FACTORY_TUNABLES = { 'wait_stat_and_value': TunableTuple( description= '\n The stat and initial value on the dog that decides when we should\n walk to the next node in the situation. The timer for this state is\n a fallback if the Sim and dog end up taking too long.\n ', stat=Commodity.TunableReference( description= '\n The stat we track on the Dog, to notify us when the Sim should attempt to walk\n to the next attractor point.\n \n When the stat reaches its convergence value, we enter the walk state.\n ' ), initial_value=Tunable( description= '\n The initial value we should set on the Dog to decide when they should walk again. \n ', tunable_type=float, default=5)) } def __init__(self, *args, wait_stat_and_value=None, **kwargs): super().__init__(*args, **kwargs) self.wait_stat_and_value = wait_stat_and_value self.wait_listener = None def on_activate(self, reader=None): super().on_activate(reader=reader) pet = self.owner.get_pet() wait_stat = pet.commodity_tracker.get_statistic( self.wait_stat_and_value.stat, add=True) wait_stat.set_value(self.wait_stat_and_value.initial_value) op = operator.le if wait_stat.get_decay_rate() <= 0 else operator.ge threshold = Threshold(wait_stat.convergence_value, op) if threshold.compare(wait_stat.get_value()): self.on_wait_stat_zero(wait_stat) else: self.wait_listener = wait_stat.create_and_add_callback_listener( threshold, self.on_wait_stat_zero) def remove_wait_listener(self): pet = self.owner.get_pet() if pet is not None: if self.wait_listener is not None: pet.commodity_tracker.remove_listener(self.wait_listener) pet.commodity_tracker.remove_statistic( self.wait_stat_and_value.stat) self.wait_listener = None def on_deactivate(self): self.remove_wait_listener() super().on_deactivate() def on_wait_stat_zero(self, stat): self.remove_wait_listener() self.owner.walk_onward() def timer_expired(self): self.owner.walk_onward()
def get_maximum_change(self, stat: Commodity): current_value = stat.get_value() target_value = self.get_goal_value(stat) max_to_value = target_value - stat.max_value min_to_value = target_value - stat.min_value if abs(max_to_value) > abs(min_to_value): if current_value >= target_value: return max_to_value return -max_to_value if current_value < target_value: return min_to_value return -min_to_value
class CivicInspectorSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'inspector_job_and_role_state': TunableSituationJobAndRoleState(description='\n The job and role state for the eco-inspector.\n ', tuning_group=GroupNames.ROLES), 'inspector_entry': _InspectorEntryState.TunableFactory(description='\n Inspector Entry State. Listens for portal allowance.\n ', display_name='1. Inspector Entry State', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'civic_inspect_outside': _InspectorOutsideState.TunableFactory(description='\n Inspecting from outside, not allowed inside.\n ', display_name='2. Civic Inspect Outside', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'civic_inspect_inside': _InspectorInsideState.TunableFactory(description='\n Allowed inside, but may inspect outside if not objects.\n ', display_name='3. Civic Inspect Inside', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'final_checks': _FinalChecksState.TunableFactory(description='\n Checks if civic policy is being followed using the conditions\n described in tuning. \n ', display_name='4. Final Checks', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'leave': _LeaveState.TunableFactory(description='\n Final checks before leaving.\n ', display_name='5. Leaving state.', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'inspection_successful_notification': UiDialogNotification.TunableFactory(description='\n A TNS that is displayed to inform of successful inspection.\n '), 'inspection_failure_notification': UiDialogNotification.TunableFactory(description='\n A TNS that is displayed to inform of failed inspection.\n '), 'inspection_partial_success_notification': UiDialogNotification.TunableFactory(description='\n A TNS that is displayed to inform of failed inspection.\n '), 'commodity_notfollow': Commodity.TunableReference(description='\n lot commodity that we set when we want a multiplier for bill modifier.\n '), 'commodity_follow': Commodity.TunableReference(description='\n lot commodity that we set when we want a multiplier for bill modifier.\n '), 'overlook_issues_success_interaction': TunableInteractionOfInterest(description='\n Interaction pushed to indicate success of overlooking issues.\n ')} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.follow_status = PolicyFollowing.FULL_FOLLOWED self.policies_not_followed = [] @classmethod def _states(cls): return (SituationStateData(1, _WaitState), SituationStateData(2, _InspectorEntryState, factory=cls.inspector_entry), SituationStateData(3, _InspectorInsideState, factory=cls.civic_inspect_inside), SituationStateData(4, _InspectorOutsideState, factory=cls.civic_inspect_outside), SituationStateData(5, _FinalChecksState, factory=cls.final_checks), SituationStateData(6, _LeaveState, factory=cls.leave)) @classmethod def default_job(cls): pass @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.inspector_job_and_role_state.job, cls.inspector_job_and_role_state.role_state)] def _on_add_sim_to_situation(self, sim, job_type, role_state_type_override=None): super()._on_add_sim_to_situation(sim, job_type, role_state_type_override=role_state_type_override) if self.inspector_person() is not None: self._change_state(self.inspector_entry()) def _show_inspection_notification(self, inspection_notification, **kwargs): inspector = self.inspector_person() if inspector is not None: dialog = inspection_notification(inspector, resolver=SingleSimResolver(inspector.sim_info)) dialog.show_dialog(**kwargs) def notify_result_and_push_bill_modifier(self): lot = services.active_lot() additional_tokens = () if self.policies_not_followed: additional_tokens = (LocalizationHelperTuning.get_bulleted_list(None, *self.policies_not_followed),) if self.follow_status == PolicyFollowing.NOT_FOLLOWED: lot.set_stat_value(self.commodity_notfollow, self.commodity_notfollow.max_value_tuning) self._show_inspection_notification(self.inspection_failure_notification, additional_tokens=additional_tokens) elif self.follow_status == PolicyFollowing.PARTIAL_FOLLOWED: self._show_inspection_notification(self.inspection_partial_success_notification, additional_tokens=additional_tokens) else: lot.set_stat_value(self.commodity_follow, self.commodity_follow.max_value_tuning) self._show_inspection_notification(self.inspection_successful_notification) def inspector_person(self): sim = next(self.all_sims_in_job_gen(self.inspector_job_and_role_state.job), None) return sim def start_situation(self): super().start_situation() self._change_state(_WaitState())
class AutonomyModifier: __qualname__ = 'AutonomyModifier' STATISTIC_RESTRICTIONS = (statistics.commodity.Commodity, statistics.statistic.Statistic, statistics.skill.Skill) FACTORY_TUNABLES = {'description': "\n An encapsulation of a modification to Sim behavior. These objects\n are passed to the autonomy system to affect things like scoring,\n which SI's are available, etc.\n ", 'super_affordance_compatibility': TunableAffordanceFilterSnippet(description='\n Tune this to provide suppression to certain affordances when an object has\n this autonomy modifier.\n EX: Tune this to exclude all on the buff for the maid to prevent\n other sims from trying to chat with the maid while the maid is\n doing her work.\n To tune if this restriction is for autonomy only, etc, see\n super_affordance_suppression_mode.\n Note: This suppression will also apply to the owning sim! So if you\n prevent people from autonomously interacting with the maid, you\n also prevent the maid from doing self interactions. To disable\n this, see suppress_self_affordances.\n '), 'super_affordance_suppression_mode': TunableEnumEntry(description='\n Setting this defines how to apply the settings tuned in Super Affordance Compatibility.', tunable_type=SuperAffordanceSuppression, default=SuperAffordanceSuppression.AUTONOMOUS_ONLY), 'super_affordance_suppress_on_add': Tunable(description='\n If checked, then the suppression rules will be applied when the\n modifier is added, potentially canceling interactions the owner is\n running.\n ', tunable_type=bool, default=False), 'suppress_self_affordances': Tunable(description="\n If checked, the super affordance compatibility tuned for this \n autonomy modifier will also apply to the sim performing self\n interactions.\n \n If not checked, we will not do super_affordance_compatibility checks\n if the target of the interaction is the same as the actor.\n \n Ex: Tune the maid's super_affordance_compatibility to exclude all\n so that other sims will not chat with the maid. But disable\n suppress_self_affordances so that the maid can still perform\n interactions on herself (such as her No More Work interaction\n that tells her she's finished cleaning).\n ", tunable_type=bool, default=True), 'score_multipliers': TunableMapping(description='\n Mapping of statistics to multipliers values to the autonomy\n scores. EX: giving motive_bladder a multiplier value of 2 will\n make it so that that motive_bladder is scored twice as high as\n it normally would be.\n ', key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description='\n The stat the multiplier will apply to.\n '), value_type=Tunable(float, 1, description='\n The autonomy score multiplier for the stat. Multiplies\n autonomy scores by the tuned value.\n ')), 'static_commodity_score_multipliers': TunableMapping(description='\n Mapping of statistics to multipliers values to the autonomy\n scores. EX: giving motive_bladder a multiplier value of 2 will\n make it so that that motive_bladder is scored twice as high as\n it normally would be.\n ', key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.STATIC_COMMODITY), description='\n The static commodity the multiplier will apply to.\n '), value_type=Tunable(float, 1, description='\n The autonomy score multiplier for the static commodity. Multiplies\n autonomy scores by the tuned value.\n ')), 'relationship_score_multiplier_with_buff_on_target': TunableMapping(description="\n Mapping of buffs to multipliers. The buff must exist on the TARGET sim.\n If it does, this value will be multiplied into the relationship score.\n \n Example: The make children desire to socialize with children, you can add \n this autonomy modifier to the child's age buff. You can then map it with \n a key to the child buff to apply a positive multiplier. An alternative \n would be to create a mapping to every other age and apply a multiplier that \n is smaller than 1.\n ", key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.BUFF), description='\n The buff that the target sim must have to apply this multiplier.\n '), value_type=Tunable(float, 1, description='\n The multiplier to apply.\n ')), 'locked_stats': TunableList(TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description='\n The stat the modifier will apply to.\n '), description='\n List of the stats we locked from this modifier. Locked stats\n are set to their maximum values and then no longer allowed to\n decay.\n '), 'decay_modifiers': CommodityDecayModifierMapping(description='\n Statistic to float mapping for decay modifiers for\n statistics. All decay modifiers are multiplied together along\n with the decay rate.\n '), 'skill_tag_modifiers': TunableMapping(description='\n The skill_tag to float mapping of skill modifiers. Skills with\n these tags will have their amount gained multiplied by the\n sum of all the tuned values.\n ', key_type=TunableEnumEntry(tag.Tag, tag.Tag.INVALID, description='\n What skill tag to apply the modifier on.\n '), value_type=Tunable(float, 0)), 'commodities_to_add': TunableList(TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=statistics.commodity.Commodity), description='\n Commodites that are added while this autonomy modifier is\n active. These commodities are removed when the autonomy\n modifier is removed.\n '), 'only_scored_stats': OptionalTunable(TunableList(TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS), description='\n List of statistics that will only be considered when doing\n autonomy.\n '), tuning_filter=FilterTag.EXPERT_MODE, description="\n If enabled, the sim in this role state will consider ONLY these\n stats when doing autonomy. EX: for the maid, only score\n commodity_maidrole_clean so she doesn't consider doing things\n that she shouldn't care about.\n "), 'only_scored_static_commodities': OptionalTunable(TunableList(StaticCommodity.TunableReference(), description='\n List of statistics that will only be considered when doing\n autonomy.\n '), tuning_filter=FilterTag.EXPERT_MODE, description='\n If enabled, the sim in this role state will consider ONLY these\n static commodities when doing autonomy. EX: for walkbys, only\n consider the ringing the doorbell\n '), 'stat_use_multiplier': TunableMapping(description='\n List of stats and multiplier to affect their increase-decrease.\n All stats on this list whenever they get modified (e. by a \n constant modifier on an interaction, an interaction result...)\n will apply the multiplier to their modified values. \n e. A toilet can get a multiplier to decrease the repair rate\n when its used, for this we would tune the commodity\n brokenness and the multiplier 0.5 (to decrease its effect)\n This tunable multiplier will affect the object statistics\n not the ones for the sims interacting with it.\n ', key_type=TunableReference(services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description='\n The stat the multiplier will apply to.\n '), value_type=TunableTuple(description='\n Float value to apply to the statistic whenever its\n affected. Greater than 1.0 if you want to increase.\n Less than 1.0 if you want a decrease (>0.0). \n A value of 0 is considered invalid and is skipped.\n ', multiplier=Tunable(description='\n Float value to apply to the statistic whenever its\n affected. Greater than 1.0 if you want to increase.\n Less than 1.0 if you want a decrease (>0.0). \n A value of 0 is considered invalid and is skipped.\n ', tunable_type=float, default=1.0), apply_direction=TunableEnumEntry(StatisticChangeDirection, StatisticChangeDirection.BOTH, description='\n Direction on when the multiplier should work on the \n statistic. For example a decrease on an object \n brokenness rate, should not increase the time it takes to \n repair it.\n '))), 'relationship_multipliers': TunableMapping(description='\n List of relationship tracks and multiplier to affect their\n increase or decrease of track value. All stats on this list\n whenever they get modified (e. by a constant modifier on an\n interaction, an interaction result...) will apply the\n multiplier to their modified values. e.g. A LTR_Friendship_Main\n can get a multiplier to decrease the relationship decay when\n interacting with someone with a given trait, for this we would\n tune the relationship track LTR_Friendship_Main and the\n multiplier 0.5 (to decrease its effect)\n ', key_type=relationships.relationship_track.RelationshipTrack.TunableReference(description='\n The Relationship track the multiplier will apply to.\n '), value_type=TunableTuple(description="\n Float value to apply to the statistic whenever it's\n affected. Greater than 1.0 if you want to increase.\n Less than 1.0 if you want a decrease (>0.0).\n ", multiplier=Tunable(tunable_type=float, default=1.0), apply_direction=TunableEnumEntry(description='\n Direction on when the multiplier should work on the \n statistic. For example a decrease on an object \n brokenness rate, should not increase the time it takes to \n repair it.\n ', tunable_type=StatisticChangeDirection, default=StatisticChangeDirection.BOTH))), 'off_lot_autonomy_rule': OptionalTunable(TunableVariant(description="\n The rules to apply for how autonomy handle on-lot and off-lot\n targets.\n \n DEFAULT:\n Sims will behave according to their default behavior. Off-\n lot sims who are outside the lot's tolerance will not\n autonomously perform interactions on the lot. Sims will\n only autonomously perform off-lot interactions within their\n off-lot radius.\n ON_LOT_ONLY:\n Sims will only consider targets on the active lot. They\n will ignore the off lot radius and off lot tolerance\n settings.\n OFF_LOT_ONLY:\n Sims will only consider targets that are off the active lot.\n They will ignore the off lot tolerance settings, but they\n will respect the off lot radius.\n UNLIMITED:\n Sims will consider all objects regardless of on/off lot\n status.\n ", default_behavior=TunableTuple(description="\n Sims will behave according to their default behavior. Off-\n lot sims who are outside the lot's tolerance will not\n autonomously perform interactions on the lot. Sims will\n only autonomously perform off-lot interactions within their\n off-lot radius.\n ", locked_args={'rule': OffLotAutonomyRules.DEFAULT}, tolerance=Tunable(description='\n This is how many meters the Sim can be off of the lot while still being \n considered on the lot for the purposes of autonomy. For example, if \n this is set to 5, the sim can be 5 meters from the edge of the lot and \n still consider all the objects on the lot for autonomy. If the sim were \n to step 6 meters from the lot, the sim would be considered off the lot \n and would only score off-lot objects that are within the off lot radius.\n \n Note: If this value is set to anything below 0, it will use the global \n default in autonomy.autonomy_modes.OFF_LOT_TOLERANCE.\n ', tunable_type=float, default=-1), radius=Tunable(description='\n The radius around the sim in which he will consider off-lot objects. If it is \n 0, the Sim will not consider off-lot objects at all. This is not recommended \n since it will keep them from running any interactions unless they are already \n within the tolerance for that lot (set with Off Lot Tolerance).\n \n Note: If this value is less than zero, the range is considered infinite. The \n sim will consider every off-lot object.\n ', tunable_type=float, default=0)), on_lot_only=TunableTuple(description='\n Sims will only consider targets on the active lot.\n ', locked_args={'rule': OffLotAutonomyRules.ON_LOT_ONLY, 'tolerance': 0, 'radius': 0}), off_lot_only=TunableTuple(description='\n Sims will only consider targets that are off the active lot. \n ', locked_args={'rule': OffLotAutonomyRules.OFF_LOT_ONLY, 'tolerance': 0}, radius=Tunable(description='\n The radius around the sim in which he will consider off-lot objects. If it is \n 0, the Sim will not consider off-lot objects at all. This is not recommended \n since it will keep them from running any interactions unless they are already \n within the tolerance for that lot (set with Off Lot Tolerance).\n \n Note: If this value is less than zero, the range is considered infinite. The \n sim will consider every off-lot object.\n ', tunable_type=float, default=-1)), unlimited=TunableTuple(description='\n Sims will consider all objects regardless of on/off lot\n status.\n ', locked_args={'rule': OffLotAutonomyRules.UNLIMITED, 'tolerance': 0, 'radius': 0}), default='default_behavior')), 'override_convergence_value': OptionalTunable(description="\n If enabled it will set a new convergence value to the tuned\n statistics. The decay of those statistics will start moving\n toward the new convergence value.\n Convergence value will apply as long as these modifier is active,\n when modifier is removed, convergence value will return to default\n tuned value.\n As a tuning restriction when this modifier gets removed we will \n reset the convergence to its original value. This means that we \n don't support two states at the same time overwriting convergence\n so we should'nt tune multiple convergence overrides on the same \n object.\n ", tunable=TunableMapping(description='\n Mapping of statistic to new convergence value.\n ', key_type=Commodity.TunableReference(), value_type=Tunable(description='\n Value to which the statistic should convert to.\n ', tunable_type=int, default=0)), disabled_name='Use_default_convergence', enabled_name='Set_new_convergence_value'), 'subject': TunableVariant(description='\n Specifies to whom this autonomy modifier will apply.\n - Apply to owner: Will apply the modifiers to the object or sim who \n is triggering the modifier. \n e.g Buff will apply the modifiers to the sim when he gets the buff. \n An object will apply the modifiers to itself when it hits a state.\n - Apply to interaction participant: Will save the modifiers to \n be only triggered when the object/sim who holds the modifier \n is on an interaction. When the interaction starts the the subject\n tuned will get the modifiers during the duration of the interaction. \n e.g A sim with modifiers to apply on an object will only trigger \n when the sim is interactin with an object.\n ', apply_on_interaction_to_participant=OptionalTunable(TunableEnumFlags(description='\n Subject on which the modifiers should apply. When this is set\n it will mean that the autonomy modifiers will trigger on a \n subect different than the object where they have been added.\n e.g. a shower ill have hygiene modifiers that have to affect \n the Sim ', enum_type=ParticipantType, default=ParticipantType.Object)), default='apply_to_owner', locked_args={'apply_to_owner': False})} def __init__(self, score_multipliers=None, static_commodity_score_multipliers=None, relationship_score_multiplier_with_buff_on_target=None, super_affordance_compatibility=None, super_affordance_suppression_mode=SuperAffordanceSuppression.AUTONOMOUS_ONLY, suppress_self_affordances=False, super_affordance_suppress_on_add=False, locked_stats=set(), decay_modifiers=None, statistic_modifiers=None, skill_tag_modifiers=None, commodities_to_add=(), only_scored_stats=None, only_scored_static_commodities=None, stat_use_multiplier=None, relationship_multipliers=None, off_lot_autonomy_rule=None, override_convergence_value=None, subject=None, exclusive_si=None): self._super_affordance_compatibility = super_affordance_compatibility self._super_affordance_suppression_mode = super_affordance_suppression_mode self._suppress_self_affordances = suppress_self_affordances self._super_affordance_suppress_on_add = super_affordance_suppress_on_add self._score_multipliers = score_multipliers self._locked_stats = set(locked_stats) self._decay_modifiers = decay_modifiers self._statistic_modifiers = statistic_modifiers self._relationship_score_multiplier_with_buff_on_target = relationship_score_multiplier_with_buff_on_target self._skill_tag_modifiers = skill_tag_modifiers self._commodities_to_add = commodities_to_add self._stat_use_multiplier = stat_use_multiplier self._relationship_multipliers = relationship_multipliers self._off_lot_autonomy_rule = off_lot_autonomy_rule self._subject = subject self._override_convergence_value = override_convergence_value self._exclusive_si = exclusive_si self._skill_tag_modifiers = {} if skill_tag_modifiers: for (skill_tag, skill_tag_modifier) in skill_tag_modifiers.items(): skill_modifier = SkillTagMultiplier(skill_tag_modifier, StatisticChangeDirection.INCREASE) self._skill_tag_modifiers[skill_tag] = skill_modifier if static_commodity_score_multipliers: if self._score_multipliers is not None: self._score_multipliers = FrozenAttributeDict(self._score_multipliers, static_commodity_score_multipliers) else: self._score_multipliers = static_commodity_score_multipliers self._static_commodity_score_multipliers = static_commodity_score_multipliers self._only_scored_stat_types = None if only_scored_stats is not None: self._only_scored_stat_types = [] self._only_scored_stat_types.extend(only_scored_stats) if only_scored_static_commodities is not None: if self._only_scored_stat_types is None: self._only_scored_stat_types = [] self._only_scored_stat_types.extend(only_scored_static_commodities) def __repr__(self): return standard_auto_repr(self) @property def exclusive_si(self): return self._exclusive_si def affordance_suppressed(self, sim, aop_or_interaction, user_directed=DEFAULT): user_directed = aop_or_interaction.is_user_directed if user_directed is DEFAULT else user_directed if not self._suppress_self_affordances and aop_or_interaction.target == sim: return False affordance = aop_or_interaction.affordance if self._super_affordance_compatibility is None: return False if user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.AUTONOMOUS_ONLY: return False if not user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.USER_DIRECTED: return False return not self._super_affordance_compatibility(affordance) def locked_stats_gen(self): for stat in self._locked_stats: yield stat def get_score_multiplier(self, stat_type): if self._score_multipliers is not None and stat_type in self._score_multipliers: return self._score_multipliers[stat_type] return 1 def get_stat_multiplier(self, stat_type, participant_type): if self._stat_use_multiplier is None: return 1 if self._subject == participant_type and stat_type in self._stat_use_multiplier: return self._stat_use_multiplier[stat_type].multiplier return 1 @property def subject(self): return self._subject @property def statistic_modifiers(self): return self._statistic_modifiers @property def statistic_multipliers(self): return self._stat_use_multiplier @property def relationship_score_multiplier_with_buff_on_target(self): return self._relationship_score_multiplier_with_buff_on_target @property def relationship_multipliers(self): return self._relationship_multipliers @property def decay_modifiers(self): return self._decay_modifiers @property def skill_tag_modifiers(self): return self._skill_tag_modifiers @property def commodities_to_add(self): return self._commodities_to_add @property def override_convergence(self): return self._override_convergence_value def is_locked(self, stat_type): if self._locked_stats and stat_type in self._locked_stats: return True return False def is_scored(self, stat_type): if self._only_scored_stat_types is None or stat_type in self._only_scored_stat_types: return True return False @property def off_lot_autonomy_rule(self): return self._off_lot_autonomy_rule @property def super_affordance_suppress_on_add(self): return self._super_affordance_suppress_on_add
class ObstacleCourseSituation(SituationComplexCommon): INSTANCE_TUNABLES = { 'coach_job_and_role_state': TunableSituationJobAndRoleState( description= '\n Job and Role State for the coach Sim. Pre-populated as\n the actor of the Situation.\n ', tuning_group=GroupNames.ROLES), 'athlete_job_and_role_state': TunableSituationJobAndRoleState( description= '\n Job and Role State for the athlete. Pre-populated as the\n target of the Situation.\n ', tuning_group=GroupNames.ROLES), 'run_course_state': RunCourseState.TunableFactory(tuning_group=GroupNames.STATE), 'obstacle_tags': TunableTags( description= '\n Tags to use when searching for obstacle course objects.\n ', filter_prefixes=('Func_PetObstacleCourse', ), minlength=1), 'setup_obstacle_state_value': ObjectStateValue.TunableReference( description= '\n The state to setup obstacles before we run the course.\n ' ), 'teardown_obstacle_state_value': ObjectStateValue.TunableReference( description= '\n The state to teardown obstacles after we run the course or when the\n situation ends.\n ' ), 'failure_commodity': Commodity.TunableReference( description= '\n The commodity we use to track how many times the athlete has failed\n to overcome an obstacle.\n ' ), 'obstacles_required': TunableRange( description= '\n The number of obstacles required for the situation to be available. \n If the obstacles that the pet can route to drops below this number,\n the situation is destroyed.\n ', tunable_type=int, default=4, minimum=1), 'unfinished_notification': UiDialogNotification.TunableFactory( description= '\n The dialog for when the situation ends prematurely or the dog never\n finishes the course.\n Token 0: Athlete\n Token 1: Coach\n Token 2: Time\n ', tuning_group=GroupNames.UI), 'finish_notifications': TunableList( description= '\n A list of thresholds and notifications to play given the outcome of\n the course. We run through the thresholds until one passes, and\n play the corresponding notification.\n ', tuning_group=GroupNames.UI, tunable=TunableTuple( description= '\n A threshold and notification to play if the threshold passes.\n ', threshold=TunableThreshold( description= '\n A threshold to compare the number of failures from the\n failure commodity when the course is finished.\n ' ), notification=UiDialogNotification.TunableFactory( description= '\n Notification to play when the situation ends.\n Token 0: Athlete\n Token 1: Coach\n Token 2: Failure Count\n Token 3: Time\n ' ))) } @classmethod def _states(cls): return (SituationStateData(0, WaitForSimJobsState), SituationStateData(1, RunCourseState, factory=cls.run_course_state)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.coach_job_and_role_state.job, cls.coach_job_and_role_state.role_state), (cls.athlete_job_and_role_state.job, cls.athlete_job_and_role_state.role_state)] @classmethod def default_job(cls): pass @classmethod def get_prepopulated_job_for_sims(cls, sim, target_sim_id=None): prepopulate = [(sim.id, cls.coach_job_and_role_state.job.guid64)] if target_sim_id is not None: prepopulate.append( (target_sim_id, cls.athlete_job_and_role_state.job.guid64)) return prepopulate @classmethod def get_obstacles(cls): object_manager = services.object_manager() found_objects = set() for tag in cls.obstacle_tags: found_objects.update( object_manager.get_objects_matching_tags({tag})) return found_objects @classmethod def is_situation_available(cls, *args, **kwargs): obstacles = cls.get_obstacles() if len(obstacles) < cls.obstacles_required: return TestResult(False, 'Not enough obstacles.') return super().is_situation_available(*args, **kwargs) @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.LOT def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) reader = self._seed.custom_init_params_reader if reader is not None: obstacles = self.get_obstacles() if not obstacles: self._self_destruct() self._obstacle_ids = {obstacle.id for obstacle in obstacles} self._course_start_time = DateAndTime( reader.read_uint64(OBSTACLE_COURSE_START_TIME_TOKEN, services.time_service().sim_now)) self._course_end_time = DateAndTime( reader.read_uint64(OBSTACLE_COURSE_END_TIME_TOKEN, services.time_service().sim_now)) else: self._obstacle_ids = set() self._course_start_time = None self._course_end_time = None self._course_progress = ObstacleCourseProgress.NOT_STARTED @property def course_progress(self): return self._course_progress @property def obstacle_ids(self): return self._obstacle_ids def _save_custom_situation(self, writer): super()._save_custom_situation(writer) if self._course_start_time is not None: writer.write_uint64(OBSTACLE_COURSE_START_TIME_TOKEN, int(self._course_start_time)) if self._course_end_time is not None: writer.write_uint64(OBSTACLE_COURSE_END_TIME_TOKEN, int(self._course_end_time)) def start_situation(self): super().start_situation() self._register_obstacle_course_events() self._change_state(WaitForSimJobsState()) def _on_remove_sim_from_situation(self, sim): super()._on_remove_sim_from_situation(sim) self._self_destruct() def _on_add_sim_to_situation(self, sim, job_type, *args, **kwargs): super()._on_add_sim_to_situation(sim, job_type, *args, **kwargs) if self.get_coach() is not None and self.get_athlete() is not None: object_manager = services.object_manager() obstacles = { object_manager.get(obstacle_id) for obstacle_id in self._obstacle_ids } sim_info_manager = services.sim_info_manager() users = sim_info_manager.instanced_sims_gen() for user in users: if user in self._situation_sims: continue for interaction in user.get_all_running_and_queued_interactions( ): target = interaction.target target = target.part_owner if target is not None and target.is_part else target if target is not None and target in obstacles: interaction.cancel( FinishingType.SITUATIONS, cancel_reason_msg='Obstacle Course Starting') self._change_state(self.run_course_state()) def _register_obstacle_course_events(self): services.get_event_manager().register_single_event( self, TestEvent.ObjectDestroyed) services.get_event_manager().register_single_event( self, TestEvent.OnExitBuildBuy) def _unregister_obstacle_course_events(self): services.get_event_manager().unregister_single_event( self, TestEvent.ObjectDestroyed) services.get_event_manager().unregister_single_event( self, TestEvent.OnExitBuildBuy) def handle_event(self, sim_info, event, resolver): super().handle_event(sim_info, event, resolver) if event == TestEvent.ObjectDestroyed: destroyed_object = resolver.get_resolved_arg('obj') if destroyed_object.id in self._obstacle_ids: self._obstacle_ids.remove(destroyed_object.id) if len(self._obstacle_ids) < self.obstacles_required: self._self_destruct() elif event == TestEvent.OnExitBuildBuy: self.validate_obstacle_course() def on_remove(self): coach = self.get_coach() athlete = self.get_athlete() if coach is not None and athlete is not None: if self.course_progress > ObstacleCourseProgress.NOT_STARTED and self.course_progress < ObstacleCourseProgress.FINISHED: course_end_time = services.time_service().sim_now course_time_span = course_end_time - self._course_start_time unfinished_dialog = self.unfinished_notification(coach) unfinished_dialog.show_dialog( additional_tokens=(athlete, coach, course_time_span)) athlete.commodity_tracker.remove_statistic(self.failure_commodity) self.teardown_obstacle_course() self._unregister_obstacle_course_events() super().on_remove() def start_course(self): self._course_progress = ObstacleCourseProgress.RUNNING self._course_start_time = services.time_service( ).sim_now if self._course_start_time is None else self._course_start_time def continue_course(self): self._change_state(self.run_course_state()) def finish_course(self): self._course_end_time = services.time_service().sim_now self._course_progress = ObstacleCourseProgress.FINISHED self._change_state(self.run_course_state()) def finish_situation(self): course_time_span = self._course_end_time - self._course_start_time athlete = self.get_athlete() coach = self.get_coach() failures = athlete.commodity_tracker.get_value(self.failure_commodity) for threshold_notification in self.finish_notifications: if threshold_notification.threshold.compare(failures): dialog = threshold_notification.notification(coach) dialog.show_dialog(additional_tokens=(athlete, coach, failures, course_time_span)) break else: logger.error( "Obstacle Course Situation doesn't have a threshold, notification for failure count of {}", failures) self._self_destruct() def setup_obstacle_course(self): obstacles = self.get_obstacles() if len(obstacles) < self.obstacles_required: self._self_destruct() self._obstacle_ids = {obstacle.id for obstacle in obstacles} def validate_obstacle_course(self): athlete = self.get_athlete() if athlete is None: self._self_destruct() return all_obstacles = self.get_obstacles() if len(all_obstacles) < self.obstacles_required: self._self_destruct() return valid_obstacles = set() for obstacle in all_obstacles: currentState = obstacle.get_state( self.setup_obstacle_state_value.state) if obstacle.is_connected(athlete): valid_obstacles.add(obstacle) if currentState == self.teardown_obstacle_state_value: obstacle.set_state(self.setup_obstacle_state_value.state, self.setup_obstacle_state_value, immediate=True) if currentState == self.setup_obstacle_state_value: obstacle.set_state( self.setup_obstacle_state_value.state, self.teardown_obstacle_state_value, immediate=True) elif currentState == self.setup_obstacle_state_value: obstacle.set_state(self.setup_obstacle_state_value.state, self.teardown_obstacle_state_value, immediate=True) if len(valid_obstacles) < self.obstacles_required: self._self_destruct() else: self._obstacle_ids = {obstacle.id for obstacle in valid_obstacles} def teardown_obstacle_course(self): obstacles = self.get_obstacles() for obstacle in obstacles: obstacle.set_state(self.teardown_obstacle_state_value.state, self.teardown_obstacle_state_value, immediate=True) def get_coach(self): return next( iter(self.all_sims_in_job_gen(self.coach_job_and_role_state.job)), None) def get_athlete(self): return next( iter(self.all_sims_in_job_gen( self.athlete_job_and_role_state.job)), None)
class Mood(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.mood_manager()): __qualname__ = 'Mood' INSTANCE_TUNABLES = {'mood_asm_param': OptionalTunable(description='\n If set, then this mood will specify an asm parameter to affect\n animations. If not set, then the ASM parameter will be determined by\n the second most prevalent mood.\n ', tunable=Tunable(description="\n The asm parameter for Sim's mood, if not set, will use 'xxx'\n from instance name pattern with 'mood_xxx'.\n ", tunable_type=str, default='', source_query=SourceQueries.SwingEnumNamePattern.format('mood')), enabled_name='Specify', disabled_name='Determined_By_Other_Moods'), 'intensity_thresholds': TunableList(int, description='\n List of thresholds at which the intensity of this mood levels up.\n If empty, this mood has a single threshold and all mood tuning lists should\n have a single item in them.\n For each threshold added, you may add a new item to the Buffs, Mood Names,\n Portrait Pose Indexes and Portrait Frames lists.'), 'buffs': TunableList(TunableBuffReference(reload_dependent=True), description='\n A list of buffs that will be added while this mood is the active mood\n on a Sim. \n The first item is applied for the initial intensity, and each\n subsequent item replaces the previous buff as the intensity levels up.'), 'mood_names': TunableList(TunableLocalizedString(), description='\n A list of localized names of this mood.\n The first item is applied for the initial intensity, and each\n subsequent item replaces the name as the intensity levels up.', export_modes=(ExportModes.ServerXML, ExportModes.ClientBinary)), 'portrait_pose_indexes': TunableList(Tunable(tunable_type=int, default=0), description='\n A list of the indexes of the pose passed to thumbnail generation on the\n client to pose the Sim portrait when they have this mood.\n You can find the list of poses in tuning\n (Client_ThumnailPoses)\n The first item is applied for the initial intensity, and each\n subsequent item replaces the pose as the intensity levels up.', export_modes=(ExportModes.ClientBinary,)), 'portrait_frames': TunableList(Tunable(tunable_type=str, default=''), description='\n A list of the frame labels (NOT numbers!) from the UI .fla file that the\n portrait should be set to when this mood is active. Determines\n background color, font color, etc.\n The first item is applied for the initial intensity, and each\n subsequent item replaces the pose as the intensity levels up.', export_modes=(ExportModes.ClientBinary,)), 'environment_scoring_commodity': Commodity.TunableReference(description="\n Defines the ranges and corresponding buffs to apply for this\n mood's environmental contribution.\n \n Be sure to tune min, max, and the different states. The\n convergence value is what will remove the buff. Suggested to be\n 0.\n "), 'descriptions': TunableList(TunableLocalizedString(), description='\n Description for the UI tooltip, per intensity.', export_modes=(ExportModes.ClientBinary,)), 'icons': TunableList(TunableResourceKey(None, resource_types=sims4.resources.CompoundTypes.IMAGE), description='\n Icon for the UI tooltip, per intensity.', export_modes=(ExportModes.ClientBinary,)), 'descriptions_age_override': TunableMapping(description='\n Mapping of age to descriptions text for mood. If age does not\n exist in mapping will use default description text.\n ', key_type=TunableEnumEntry(sim_info_types.Age, sim_info_types.Age.CHILD), value_type=TunableList(description='\n Description for the UI tooltip, per intensity.\n ', tunable=TunableLocalizedString()), key_name='Age', value_name='description_text', export_modes=(ExportModes.ClientBinary,)), 'descriptions_trait_override': TunableMoodDescriptionTraitOverride(description='\n Trait override for mood descriptions. If a Sim has this trait\n and there is not a valid age override for the Sim, this\n description text will be used.\n ', export_modes=(ExportModes.ClientBinary,)), 'audio_stings_on_add': TunableList(description="\n The audio to play when a mood or it's intensity changes. Tune one for each intensity on the mood.\n ", tunable=TunableResourceKey(description='\n The sound to play.\n ', default=None, resource_types=(sims4.resources.Types.PROPX,), export_modes=ExportModes.ClientBinary)), 'mood_colors': TunableList(description='\n A list of the colors displayed on the steel series mouse when the active Sim has this mood. The first item is applied for the initial intensity, and each subsequent item replaces the color as the intensity levels up.\n ', tunable=TunableVector3(description='\n Color.\n ', default=sims4.math.Vector3.ZERO(), export_modes=ExportModes.ClientBinary)), 'mood_frequencies': TunableList(description='\n A list of the flash frequencies on the steel series mouse when the active Sim has this mood. The first item is applied for the initial intensity, and each subsequent item replaces the value as the intensity levels up. 0 => solid color, otherwise, value => value hertz.\n ', tunable=Tunable(tunable_type=float, default=0.0, description=',\n Hertz.\n ', export_modes=ExportModes.ClientBinary)), 'buff_polarity': TunableEnumEntry(description='\n Setting the polarity will determine how up/down arrows\n appear for any buff that provides this mood.\n ', tunable_type=BuffPolarity, default=BuffPolarity.NEUTRAL, tuning_group=GroupNames.UI, needs_tuning=True, export_modes=ExportModes.All), 'is_changeable': Tunable(description='\n If this is checked, any buff with this mood will change to\n the highest current mood of the same polarity. If there is no mood\n with the same polarity it will default to use this mood type\n ', tunable_type=bool, default=False, needs_tuning=True)} _asm_param_name = None excluding_traits = None @classmethod def _tuning_loaded_callback(cls): cls._asm_param_name = cls.mood_asm_param if not cls._asm_param_name: name_list = cls.__name__.split('_', 1) if len(name_list) <= 1: logger.error("Mood {} has an invalid name for asm parameter, please either set 'mood_asm_param' or change the tuning file name to match the format 'mood_xxx'.", cls.__name__) cls._asm_param_name = name_list[1] cls._asm_param_name = cls._asm_param_name.lower() for buff_ref in cls.buffs: my_buff = buff_ref.buff_type while my_buff is not None: if my_buff.mood_type is not None: logger.error('Mood {} will apply a buff ({}) that affects mood. This can cause mood calculation errors. Please select a different buff or remove the mood change.', cls.__name__, my_buff.mood_type.__name__) my_buff.is_mood_buff = True prev_threshold = 0 for threshold in cls.intensity_thresholds: if threshold <= prev_threshold: logger.error('Mood {} has Intensity Thresholds in non-ascending order.') break prev_threshold = threshold @classmethod def _verify_tuning_callback(cls): num_thresholds = len(cls.intensity_thresholds) + 1 if len(cls.buffs) != num_thresholds: logger.error('Mood {} does not have the correct number of Buffs tuned. It has {} thresholds, but {} buffs.', cls.__name__, num_thresholds, len(cls.buffs)) if len(cls.mood_names) != num_thresholds: logger.error('Mood {} does not have the correct number of Mood Names tuned. It has {} thresholds, but {} names.', cls.__name__, num_thresholds, len(cls.mood_names)) if len(cls.portrait_pose_indexes) != num_thresholds: logger.error('Mood {} does not have the correct number of Portrait Pose Indexes tuned. It has {} thresholds, but {} poses.', cls.__name__, num_thresholds, len(cls.portrait_pose_indexes)) if len(cls.portrait_frames) != num_thresholds: logger.error('Mood {} does not have the correct number of Portrait Frames tuned. It has {} thresholds, but {} frames.', cls.__name__, num_thresholds, len(cls.portrait_frames)) for (age, descriptions) in cls.descriptions_age_override.items(): while len(descriptions) != num_thresholds: logger.error('Mood {} does not have the correct number of descriptions age override tuned. For age:({}) It has {} thresholds, but {} descriptions.', cls.__name__, age, num_thresholds, len(descriptions)) if cls.descriptions_trait_override.trait is not None and len(cls.descriptions_trait_override.descriptions) != num_thresholds: logger.error('Mood {} does not have the correct number of trait override descriptions tuned. For trait:({}) It has {} thresholds, but {} descriptions.', cls.__name__, cls.descriptions_trait_override.trait.__name__, num_thresholds, len(cls.descriptions_trait_override.descriptions)) @classproperty def asm_param_name(cls): return cls._asm_param_name
class Puddle(objects.game_object.GameObject): WEED_DEFINITIONS = TunableDefinitionList(description='\n Possible weed objects which can be spawned by evaporation.') PLANT_DEFINITIONS = TunableDefinitionList(description='\n Possible plant objects which can be spawned by evaporation.') INSTANCE_TUNABLES = {'indoor_evaporation_time': TunableInterval(description='\n Number of SimMinutes this puddle should take to evaporate when \n created indoors.\n ', tunable_type=TunableSimMinute, default_lower=200, default_upper=300, minimum=1, tuning_group=GroupNames.DEPRECATED), 'outdoor_evaporation_time': TunableInterval(description='\n Number of SimMinutes this puddle should take to evaporate when \n created outdoors.\n ', tunable_type=TunableSimMinute, default_lower=30, default_upper=60, minimum=1, tuning_group=GroupNames.DEPRECATED), 'evaporation_outcome': TunableTuple(nothing=TunableRange(int, 5, minimum=1, description='Relative chance of nothing.'), weeds=TunableRange(int, 2, minimum=0, description='Relative chance of weeds.'), plant=TunableRange(int, 1, minimum=0, description='Relative chance of plant.'), tuning_group=GroupNames.PUDDLES), 'intial_stat_value': TunableTuple(description='\n This is the starting value for the stat specified. This controls \n how long it takes to mop this puddle.\n ', stat=Statistic.TunableReference(description='\n The stat used for mopping puddles.\n '), value=Tunable(description='\n The initial value this puddle should have for the mopping stat.\n The lower the value (-100,100), the longer it takes to mop up.\n ', tunable_type=int, default=-20), tuning_group=GroupNames.PUDDLES), 'evaporation_data': TunableTuple(description='\n This is the information for evaporation. This controls how long this\n puddle takes to evaporate.\n ', commodity=Commodity.TunableReference(description='\n The commodity used for evaporation.\n '), initial_value=TunableInterval(description='\n Initial value of this commodity. Time it takes to evaporate\n will be based on how fast this commodity decays.\n (Based on loot given in weather aware component)\n ', tunable_type=float, default_lower=30, default_upper=60, minimum=1), tuning_group=GroupNames.PUDDLES), 'puddle_liquid': TunableEnumEntry(description='\n The liquid that the puddle is made of.\n ', tunable_type=PuddleLiquid, default=PuddleLiquid.INVALID, invalid_enums=(PuddleLiquid.INVALID,), tuning_group=GroupNames.PUDDLES), 'puddle_size': TunableEnumEntry(description='\n The size of the puddle.\n ', tunable_type=PuddleSize, default=PuddleSize.NoPuddle, invalid_enums=(PuddleSize.NoPuddle,), tuning_group=GroupNames.PUDDLES), 'puddle_grow_chance': TunableMultiplier.TunableFactory(description='\n The chance of puddle to grow.\n ', tuning_group=GroupNames.PUDDLES)} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._evaporate_callback_handle = None self.statistic_tracker.set_value(self.intial_stat_value.stat, self.intial_stat_value.value) @property def size_count(self): if self.puddle_size == PuddleSize.SmallPuddle: return 1 if self.puddle_size == PuddleSize.MediumPuddle: return 2 elif self.puddle_size == PuddleSize.LargePuddle: return 3 def place_puddle(self, target, max_distance, ids_to_ignore=DEFAULT): destroy_puddle = True try: if ids_to_ignore is DEFAULT: ids_to_ignore = (self.id,) else: ids_to_ignore.append(self.id) flags = placement.FGLSearchFlag.ALLOW_GOALS_IN_SIM_POSITIONS flags = flags | placement.FGLSearchFlag.ALLOW_GOALS_IN_SIM_INTENDED_POSITIONS flags = flags | placement.FGLSearchFlag.STAY_IN_SAME_CONNECTIVITY_GROUP if target.is_on_active_lot(): flags = flags | placement.FGLSearchFlag.SHOULD_TEST_BUILDBUY else: flags = flags | placement.FGLSearchFlag.SHOULD_TEST_ROUTING flags = flags | placement.FGLSearchFlag.USE_SIM_FOOTPRINT flags = flags | placement.FGLSearchFlag.CALCULATE_RESULT_TERRAIN_HEIGHTS flags = flags | placement.FGLSearchFlag.DONE_ON_MAX_RESULTS radius_target = target while radius_target.parent is not None: radius_target = radius_target.parent if radius_target.is_part: radius_target = radius_target.part_owner routing_surface = target.routing_surface routing_surface = SurfaceIdentifier(routing_surface.primary_id, routing_surface.secondary_id, SurfaceType.SURFACETYPE_WORLD) starting_location = placement.create_starting_location(position=target.position + target.forward*radius_target.object_radius, orientation=sims4.random.random_orientation(), routing_surface=routing_surface) fgl_context = placement.create_fgl_context_for_object(starting_location, self, search_flags=flags, ignored_object_ids=ids_to_ignore, max_distance=max_distance) (position, orientation) = placement.find_good_location(fgl_context) if position is not None: destroy_puddle = False self.place_puddle_at(position, orientation, routing_surface) return True return False finally: if destroy_puddle: self.destroy(source=self, cause='Failed to place puddle.') def place_puddle_at(self, position, orientation, routing_surface): self.location = sims4.math.Location(sims4.math.Transform(position, orientation), routing_surface) self.fade_in() self.start_evaporation() def try_grow_puddle(self): if self.puddle_size == PuddleSize.LargePuddle: return resolver = SingleObjectResolver(self) chance = self.puddle_grow_chance.get_multiplier(resolver) if random.random() > chance: return else: if self.puddle_size == PuddleSize.MediumPuddle: puddle = create_puddle(PuddleSize.LargePuddle, puddle_liquid=self.puddle_liquid) else: puddle = create_puddle(PuddleSize.MediumPuddle, puddle_liquid=self.puddle_liquid) if puddle.place_puddle(self, 1, ids_to_ignore=[self.id]): if self._evaporate_callback_handle is not None: self.commodity_tracker.remove_listener(self._evaporate_callback_handle) self.destroy(self, cause='Puddle is growing.', fade_duration=ClientObjectMixin.FADE_DURATION) return puddle def start_evaporation(self): tracker = self.commodity_tracker tracker.set_value(self.evaporation_data.commodity, self.evaporation_data.initial_value.random_float()) if self._evaporate_callback_handle is not None: tracker.remove_listener(self._evaporate_callback_handle) threshold = sims4.math.Threshold(0.0, operator.le) self._evaporate_callback_handle = tracker.create_and_add_listener(self.evaporation_data.commodity, threshold, self.evaporate) def evaporate(self, stat_instance): if self.in_use: self.start_evaporation() return if self._evaporate_callback_handle is not None: self.commodity_tracker.remove_listener(self._evaporate_callback_handle) self._evaporate_callback_handle = None if self.is_on_natural_ground(): defs_to_make = sims4.random.weighted_random_item([(self.evaporation_outcome.nothing, None), (self.evaporation_outcome.weeds, self.WEED_DEFINITIONS), (self.evaporation_outcome.plant, self.PLANT_DEFINITIONS)]) if defs_to_make: def_to_make = random.choice(defs_to_make) obj_location = sims4.math.Location(sims4.math.Transform(self.position, sims4.random.random_orientation()), self.routing_surface) (result, _) = build_buy.test_location_for_object(None, def_to_make.id, obj_location, [self]) if result: obj = objects.system.create_object(def_to_make) obj.opacity = 0 obj.location = self.location obj.fade_in() self.destroy(self, cause='Puddle is evaporating.', fade_duration=ClientObjectMixin.FADE_DURATION) def load_object(self, object_data, **kwargs): super().load_object(object_data, **kwargs) self.start_evaporation()
class RetailManager(BusinessManager): NPC_STORE_FOR_SALE_TAG = TunableEnumEntry( description= '\n Objects with this tag will be set For Sale when an NPC store is\n visited.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, pack_safe=True) NPC_STORE_MANNEQUIN_TAG = TunableEnumEntry( description= '\n Objects with this tag will have their mannequin component outfits\n restocked any time a premade NPC store is visited.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, pack_safe=True) FOR_SALE_VFX = PlayEffect.TunableFactory( description= '\n An effect that can be toggled on/off for all objects marked for sale.\n ' ) ITEMS_SENT_TO_HH_INVENTORY_NOTIFICATION = TunableUiDialogNotificationSnippet( description= "\n The notification that shows up when items are sent to the household's\n inventory because the item that these things are slotted to are\n sold.\n " ) NPC_STORE_ITEM_COMMODITIES_TO_MAX_ON_OPEN = TunableList( description= '\n A list of commodities that should get maxed out on retail objects\n when an NPC store opens.\n ', tunable=Commodity.TunableReference( description='\n The commodity to max out.\n ', pack_safe=True)) def __init__(self): super().__init__(BusinessType.RETAIL) self._objs_with_for_sale_vfx = {} self._for_sale_vfx_toggle_value = False def on_client_disconnect(self): self.remove_for_sale_vfx_from_all_objects() def set_advertising_type(self, advertising_type): commodity = RetailTuning.ADVERTISING_COMMODITY_MAP.get( advertising_type, None) if commodity is not None: tracker = services.active_lot().commodity_tracker tracker.remove_statistic(commodity) tracker.add_statistic(commodity) def get_advertising_type_for_gsi(self): if services.current_zone_id() == self._zone_id: tracker = services.active_lot().commodity_tracker commodities = RetailTuning.ADVERTISING_COMMODITY_MAP.values() return str([(c, tracker.get_value(c)) for c in commodities if tracker.has_statistic(c)]) else: return '' def get_curb_appeal(self): total_curb_appeal = sum( obj.retail_component.get_current_curb_appeal() for obj in RetailUtils.get_all_retail_objects()) return total_curb_appeal + self._get_lot_advertising_commodity_sum() def _get_lot_advertising_commodity_sum(self): return sum(self._get_lot_advertising_commodity_values()) def _get_lot_advertising_commodity_values(self): tracker = services.active_lot().commodity_tracker commodities = RetailTuning.ADVERTISING_COMMODITY_MAP.values() return [ tracker.get_value(c) for c in commodities if tracker.has_statistic(c) ] def _fixup_placard_if_necessary(self, obj): obj_retail_component = obj.retail_component if obj_retail_component is not None and obj_retail_component.is_sold: obj_retail_component._change_to_placard(play_vfx=False) def should_automatically_close(self): return self.is_owner_household_active and ( self._zone_id is not None and self._zone_id != services.current_zone_id()) def _should_make_customer(self, sim_info): return False def on_protocols_loaded(self): self._employee_manager.reload_employee_uniforms() def refresh_for_sale_vfx_for_object(self, obj): if obj.retail_component is None: logger.error( 'Attempting to toggle for sale vfx on an object {} with no retail component.', obj, owner='tastle') return show_vfx = self._for_sale_vfx_toggle_value if obj.retail_component.is_for_sale else False self._update_for_sale_vfx_for_object(obj, show_vfx) def _update_for_sale_vfx_for_object(self, obj, toggle_value): if obj not in self._objs_with_for_sale_vfx and toggle_value: self._objs_with_for_sale_vfx[obj] = self.FOR_SALE_VFX(obj) self._objs_with_for_sale_vfx[obj].start() elif obj in self._objs_with_for_sale_vfx and not toggle_value: obj_vfx = self._objs_with_for_sale_vfx.pop(obj) obj_vfx.stop() def remove_for_sale_vfx_from_all_objects(self): self._for_sale_vfx_toggle_value = False for obj_vfx in self._objs_with_for_sale_vfx.values(): obj_vfx.stop() self._objs_with_for_sale_vfx.clear() def toggle_for_sale_vfx(self): self._for_sale_vfx_toggle_value = not self._for_sale_vfx_toggle_value for item in RetailUtils.all_retail_objects_gen( allow_sold=False, include_inventories=False): self._update_for_sale_vfx_for_object( item, self._for_sale_vfx_toggle_value) def update_retail_objects_commodities(self): for retail_obj in RetailUtils.all_retail_objects_gen(allow_sold=False): retail_obj.update_component_commodity_flags() def _open_business(self): super()._open_business() if self._owner_household_id is not None: self.update_retail_objects_commodities() def _close_business(self, play_sound=True): if not self._is_open: return super()._close_business(play_sound) if self._owner_household_id is not None: self.update_retail_objects_commodities() def get_median_item_value(self): values = [ obj.retail_component.get_retail_value() for obj in RetailUtils.all_retail_objects_gen() ] if not values: return 0 values.sort() count = len(values) midpoint = count // 2 if count % 2: return values[midpoint] return (values[midpoint] + values[midpoint - 1]) / 2 def should_show_no_way_to_make_money_notification(self): return not self.has_any_object_for_sale() def has_any_object_for_sale(self): for retail_obj in RetailUtils.all_retail_objects_gen( allow_not_for_sale=True): if retail_obj.retail_component.is_for_sale: return True if retail_obj.is_in_inventory(): return True return False def on_zone_load(self): super().on_zone_load() for obj in RetailUtils.get_all_retail_objects(): self._fixup_placard_if_necessary(obj) if services.current_zone_id() != self._zone_id: return tracker = services.active_lot().commodity_tracker advertising_commodities = RetailTuning.ADVERTISING_COMMODITY_MAP.values( ) for advertising_commodity in advertising_commodities: commodity = tracker.get_statistic(advertising_commodity) if commodity is not None: commodity.decay_enabled = True def _open_pure_npc_store(self, is_premade): has_retail_obj = False for retail_obj in RetailUtils.all_retail_objects_gen( allow_not_for_sale=True): self._adjust_commodities_if_necessary(retail_obj) obj_retail_component = retail_obj.retail_component if obj_retail_component.is_for_sale or retail_obj.is_in_inventory( ): has_retail_obj = True else: if is_premade: set_for_sale = retail_obj.has_tag( self.NPC_STORE_FOR_SALE_TAG) else: set_for_sale = obj_retail_component.is_sold if set_for_sale: obj_retail_component.set_for_sale() has_retail_obj = True if retail_obj.has_tag(self.NPC_STORE_MANNEQUIN_TAG): self._set_up_mannequin_during_open(retail_obj) has_retail_obj = True self.set_open(has_retail_obj) @classmethod def _set_up_mannequin_during_open(cls, mannequin): (current_outfit_type, _) = mannequin.get_current_outfit() if current_outfit_type == OutfitCategory.BATHING: mannequin.set_current_outfit(mannequin.get_previous_outfit()) def _open_household_owned_npc_store(self): should_open = False for retail_obj in RetailUtils.all_retail_objects_gen( allow_not_for_sale=True): self._adjust_commodities_if_necessary(retail_obj) if not should_open: if not retail_obj.retail_component.is_not_for_sale: should_open = True self.set_open(should_open) @classmethod def _adjust_commodities_if_necessary(cls, obj): for obj_commodity in cls.NPC_STORE_ITEM_COMMODITIES_TO_MAX_ON_OPEN: tracker = obj.get_tracker(obj_commodity) if tracker is not None: if tracker.has_statistic(obj_commodity): tracker.set_max(obj_commodity) def show_summary_dialog(self, is_from_close=False): RetailSummaryDialog.show_dialog(self, is_from_close=is_from_close) def construct_business_message(self, msg): super().construct_business_message(msg) msg.retail_data = Business_pb2.RetailBusinessDataUpdate() def get_lot_name(self): zone_data = services.get_persistence_service().get_zone_proto_buff( self._zone_id) if zone_data is None: return '' return zone_data.name
class AutonomyModifier(BaseGameEffectModifier): STATISTIC_RESTRICTIONS = (statistics.commodity.Commodity, statistics.statistic.Statistic, statistics.skill.Skill, LifeSkillStatistic, 'RankedStatistic') ALWAYS_WHITELISTED_AFFORDANCES = TunableAffordanceFilterSnippet( description= '\n Any affordances tuned to be compatible with this filter will always be\n allowed. This is useful for stuff like death and debug interactions,\n which should never be disallowed by an autonomy modifier.\n ' ) @staticmethod def _verify_tunable_callback(instance_class, tunable_name, source, value): for (situation_type, multiplier ) in value.situation_type_social_score_multiplier.items(): if multiplier == 1: logger.error( 'A situation type social score multiplier currently has a tuned multiplier of 1. This is invalid, please change to a value other than 1 or delete the entry. Class: {} Situation Type: {}', instance_class, situation_type) FACTORY_TUNABLES = { 'verify_tunable_callback': _verify_tunable_callback, 'description': "\n An encapsulation of a modification to Sim behavior. These objects\n are passed to the autonomy system to affect things like scoring,\n which SI's are available, etc.\n ", 'provided_affordance_compatibility': TunableAffordanceFilterSnippet( description= '\n Tune this to provide suppression to certain affordances when an object has\n this autonomy modifier.\n EX: Tune this to exclude all on the buff for the maid to prevent\n other sims from trying to chat with the maid while the maid is\n doing her work.\n To tune if this restriction is for autonomy only, etc, see\n super_affordance_suppression_mode.\n Note: This suppression will also apply to the owning sim! So if you\n prevent people from autonomously interacting with the maid, you\n also prevent the maid from doing self interactions. To disable\n this, see suppress_self_affordances.\n ' ), 'super_affordance_suppression_mode': TunableEnumEntry( description= '\n Setting this defines how to apply the settings tuned in Super Affordance Compatibility.', tunable_type=SuperAffordanceSuppression, default=SuperAffordanceSuppression.AUTONOMOUS_ONLY), 'super_affordance_suppress_on_add': Tunable( description= '\n If checked, then the suppression rules will be applied when the\n modifier is added, potentially canceling interactions the owner is\n running.\n ', tunable_type=bool, default=False), 'suppress_self_affordances': Tunable( description= "\n If checked, the super affordance compatibility tuned for this \n autonomy modifier will also apply to the sim performing self\n interactions.\n \n If not checked, we will not do provided_affordance_compatibility checks\n if the target of the interaction is the same as the actor.\n \n Ex: Tune the maid's provided_affordance_compatibility to exclude all\n so that other sims will not chat with the maid. But disable\n suppress_self_affordances so that the maid can still perform\n interactions on herself (such as her No More Work interaction\n that tells her she's finished cleaning).\n ", tunable_type=bool, default=True), 'score_multipliers': TunableMapping( description= '\n Mapping of statistics to multipliers values to the autonomy\n scores. EX: giving motive_bladder a multiplier value of 2 will\n make it so that that motive_bladder is scored twice as high as\n it normally would be.\n ', key_type=TunableReference( services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, description= '\n The stat the multiplier will apply to.\n ' ), value_type=Tunable( float, 1, description= '\n The autonomy score multiplier for the stat. Multiplies\n autonomy scores by the tuned value.\n ' )), 'static_commodity_score_multipliers': TunableMapping( description= '\n Mapping of statistics to multipliers values to the autonomy\n scores. EX: giving motive_bladder a multiplier value of 2 will\n make it so that that motive_bladder is scored twice as high as\n it normally would be.\n ', key_type=TunableReference( description= '\n The static commodity the multiplier will apply to.\n ', manager=services.get_instance_manager( sims4.resources.Types.STATIC_COMMODITY), pack_safe=True), value_type=Tunable( float, 1, description= '\n The autonomy score multiplier for the static commodity. Multiplies\n autonomy scores by the tuned value.\n ' )), 'relationship_score_multiplier_with_buff_on_target': TunableMapping( description= "\n Mapping of buffs to multipliers. The buff must exist on the TARGET sim.\n If it does, this value will be multiplied into the relationship score.\n \n Example: The make children desire to socialize with children, you can add \n this autonomy modifier to the child's age buff. You can then map it with \n a key to the child buff to apply a positive multiplier. An alternative \n would be to create a mapping to every other age and apply a multiplier that \n is smaller than 1.\n ", key_type=TunableReference( services.get_instance_manager(sims4.resources.Types.BUFF), description= '\n The buff that the target sim must have to apply this multiplier.\n ' ), value_type=Tunable( float, 1, description= '\n The multiplier to apply.\n ' )), 'locked_stats': TunableList( TunableReference( services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, pack_safe=True, description= '\n The stat the modifier will apply to.\n ' ), description= '\n List of the stats we locked from this modifier. Locked stats\n are set to their maximum values and then no longer allowed to\n decay.\n ' ), 'locked_stats_autosatisfy_on_unlock': Tunable( description= '\n If true, locked stats will be set to the value on the auto\n satisfy curve when unlocked. If false they will remain as-is.\n (i.e. maxed)\n ', tunable_type=bool, default=True), 'decay_modifiers': CommodityDecayModifierMapping( description= '\n Statistic to float mapping for decay modifiers for\n statistics. All decay modifiers are multiplied together along\n with the decay rate.\n ' ), 'decay_modifier_by_category': StatisticCategoryModifierMapping( description= '\n Statistic Category to float mapping for decay modifiers for\n statistics. All decay modifiers are multiplied together along with\n decay rate.\n ' ), 'skill_tag_modifiers': TunableMapping( description= '\n The skill_tag to float mapping of skill modifiers. Skills with\n these tags will have their amount gained multiplied by the\n sum of all the tuned values.\n ', key_type=TunableEnumEntry( tag.Tag, tag.Tag.INVALID, description= '\n What skill tag to apply the modifier on.\n ' ), value_type=Tunable(float, 0)), 'commodities_to_add': TunableList( TunableReference( services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=statistics.commodity.Commodity, pack_safe=True), description= '\n Commodites that are added while this autonomy modifier is\n active. These commodities are removed when the autonomy\n modifier is removed.\n ' ), 'commodities_value_persist_for_travel': Tunable( description= '\n If checked, value of commodities added from this modifier will persist\n through travel. This only works for commodities those have "Persisted Tuning"\n checked. Also please note commodities themselves won\'t persist, we record\n their value before travel and set the value back once arrived the destination.\n \n If not checked, value of commodities added from this modifier will not persist\n through travel. Commodities will be re-added and start from their initial value.\n \n Ex: Check this for Buff_Trait_Loner\'s autonomy modifier will make its attached\n Commodity_Trait_Loner_Solitude\'s value persist through travel. So when the sim\n arrives another lot, the commodity\'s value will be set back to that just before\n the travel, so its "Happy Loner" buff can persist as a consequence.\n ', tunable_type=bool, default=False), 'only_scored_stats': OptionalTunable( TunableList( TunableReference( services.get_instance_manager( sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS), description= '\n List of statistics that will only be considered when doing\n autonomy.\n ' ), tuning_filter=FilterTag.EXPERT_MODE, description= "\n If enabled, the sim in this role state will consider ONLY these\n stats when doing autonomy. EX: for the maid, only score\n commodity_maidrole_clean so she doesn't consider doing things\n that she shouldn't care about.\n " ), 'only_scored_static_commodities': OptionalTunable( TunableList( StaticCommodity.TunableReference(), description= '\n List of statistics that will only be considered when doing\n autonomy.\n ' ), tuning_filter=FilterTag.EXPERT_MODE, description= '\n If enabled, the sim in this role state will consider ONLY these\n static commodities when doing autonomy. EX: for walkbys, only\n consider the ringing the doorbell\n ' ), 'stat_use_multiplier': TunableMapping( description= '\n List of stats and multiplier to affect their increase-decrease.\n All stats on this list whenever they get modified (e. by a \n constant modifier on an interaction, an interaction result...)\n will apply the multiplier to their modified values. \n e. A toilet can get a multiplier to decrease the repair rate\n when its used, for this we would tune the commodity\n brokenness and the multiplier 0.5 (to decrease its effect)\n This tunable multiplier will affect the object statistics\n not the ones for the sims interacting with it.\n ', key_type=TunableReference( description= '\n The stat the multiplier will apply to.\n ', manager=services.get_instance_manager( sims4.resources.Types.STATISTIC), class_restrictions=STATISTIC_RESTRICTIONS, pack_safe=True), value_type=TunableTuple( description= '\n Float value to apply to the statistic whenever its\n affected. Greater than 1.0 if you want to increase.\n Less than 1.0 if you want a decrease (>0.0). \n A value of 0 is considered invalid and is skipped.\n ', multiplier=Tunable( description= '\n Float value to apply to the statistic whenever its\n affected. Greater than 1.0 if you want to increase.\n Less than 1.0 if you want a decrease (>0.0). \n A value of 0 is considered invalid and is skipped.\n ', tunable_type=float, default=1.0), apply_direction=TunableEnumEntry( description= '\n Direction on when the multiplier should work on the \n statistic. For example a decrease on an object \n brokenness rate, should not increase the time it takes to \n repair it.\n ', tunable_type=StatisticChangeDirection, default=StatisticChangeDirection.BOTH))), 'relationship_multipliers': TunableMapping( description= '\n List of relationship tracks and multiplier to affect their\n increase or decrease of track value. All stats on this list\n whenever they get modified (e. by a constant modifier on an\n interaction, an interaction result...) will apply the\n multiplier to their modified values. e.g. A LTR_Friendship_Main\n can get a multiplier to decrease the relationship decay when\n interacting with someone with a given trait, for this we would\n tune the relationship track LTR_Friendship_Main and the\n multiplier 0.5 (to decrease its effect)\n ', key_type=relationships.relationship_track.RelationshipTrack. TunableReference( description= '\n The Relationship track the multiplier will apply to.\n ' ), value_type=TunableTuple( description= "\n Float value to apply to the statistic whenever it's\n affected. Greater than 1.0 if you want to increase.\n Less than 1.0 if you want a decrease (>0.0).\n ", multiplier=Tunable(tunable_type=float, default=1.0), apply_direction=TunableEnumEntry( description= '\n Direction on when the multiplier should work on the \n statistic. For example a decrease on an object \n brokenness rate, should not increase the time it takes to \n repair it.\n ', tunable_type=StatisticChangeDirection, default=StatisticChangeDirection.BOTH))), 'object_tags_that_override_off_lot_autonomy': TunableList( description= "\n A list of object tags for objects that are always valid to be considered \n for autonomy regardless of their on-lot or off-lot status. Note that this \n will only override off-lot autonomy availability. It doesn't affect other \n ways that objects are culled out. For example, if an object list is passed\n into the autonomy request (like when we're looking at targets of a crafting \n phase), we only consider the objects in that list. This won't override that \n list.\n ", tunable=TunableEnumEntry(tunable_type=tag.Tag, default=tag.Tag.INVALID)), 'off_lot_autonomy_rule': OptionalTunable(tunable=TunableOffLotAutonomy()), 'override_convergence_value': OptionalTunable( description= "\n If enabled it will set a new convergence value to the tuned\n statistics. The decay of those statistics will start moving\n toward the new convergence value.\n Convergence value will apply as long as these modifier is active,\n when modifier is removed, convergence value will return to default\n tuned value.\n As a tuning restriction when this modifier gets removed we will \n reset the convergence to its original value. This means that we \n don't support two states at the same time overwriting convergence\n so we should'nt tune multiple convergence overrides on the same \n object.\n ", tunable=TunableMapping( description= '\n Mapping of statistic to new convergence value.\n ', key_type=Commodity.TunableReference(), value_type=Tunable( description= '\n Value to which the statistic should convert to.\n ', tunable_type=int, default=0)), disabled_name='Use_default_convergence', enabled_name='Set_new_convergence_value'), 'subject': TunableVariant( description= '\n Specifies to whom this autonomy modifier will apply.\n - Apply to owner: Will apply the modifiers to the object or sim who \n is triggering the modifier. \n e.g Buff will apply the modifiers to the sim when he gets the buff. \n An object will apply the modifiers to itself when it hits a state.\n - Apply to interaction participant: Will save the modifiers to \n be only triggered when the object/sim who holds the modifier \n is on an interaction. When the interaction starts the the subject\n tuned will get the modifiers during the duration of the interaction. \n e.g A sim with modifiers to apply on an object will only trigger \n when the sim is interactin with an object.\n ', apply_on_interaction_to_participant=OptionalTunable( TunableEnumFlags( description= '\n Subject on which the modifiers should apply. When this is set\n it will mean that the autonomy modifiers will trigger on a \n subect different than the object where they have been added.\n e.g. a shower ill have hygiene modifiers that have to affect \n the Sim ', enum_type=ParticipantType, default=ParticipantType.Object)), default='apply_to_owner', locked_args={'apply_to_owner': False}), 'suppress_preroll_autonomy': Tunable( description= '\n If checked, sims with this buff will not run preroll autonomy when\n first loading into a lot. This means that when the loading screen\n disappears, they will be standing exactly where they spawned,\n looking like a chump, instead of being somewhere on the lot doing\n a normal-looking activity. As soon as the loading screen disappears,\n all bets are off and autonomy will run normally again.\n ', tunable_type=bool, default=False), 'situation_type_social_score_multiplier': TunableMapping( description= '\n A tunable mapping form situation type to multiplier to apply\n when the target Sim is in a situation of the specified type with\n the actor Sim.\n ', key_type=TunableReference( description= '\n A reference to the type of situation that both Sims need to\n be in together in order for the multiplier to be applied.\n ', manager=services.get_instance_manager( sims4.resources.Types.SITUATION), pack_safe=True), value_type=Tunable( description= '\n The multiplier to apply.\n ', tunable_type=float, default=1)), 'transition_from_sit_posture_penalty': OptionalTunable( description= '\n When enabled causes the Sim to be penalized for transitioning\n from Sit to another posture.\n ', tunable=Tunable( description= '\n The multiplier to apply to the autonomous interaction score\n as a result of the Sim transitioning from sit to something\n else.\n \n This number should be less than one (<1) in order for it to be\n a penalty, otherwise it will be a bonus.\n ', tunable_type=float, default=1.0)), 'supress_outside_objects_if_sun_out': OptionalTunable( description= "\n When enabled, objects on the outside will be suppressed by autonomy\n if the sun is out (i.e. region provides light, it's daytime, \n and weather isn't too cloudy) and will not be used unless they have\n interactions tagged with 'counts_as_inside'.\n ", tunable=Tunable( description= '\n When checked, outside objects will be suppressed, otherwise\n supression will be canceled.\n Canceling suppression will have a higher priority than an\n active supression, this is to support cases like vampire buffs\n always being suppressed, but when they activate the daywalker\n power, that cancelation of suppression should always have a \n higher priority. \n ', tunable_type=bool, default=True)), 'outside_objects_multiplier': OptionalTunable( description= "\n When enabled, objects that are outside will have their interaction \n scores modified unless they are tagged with 'counts_as_inside'.\n ", tunable=Tunable( description= '\n Amount to multiple the autonomy score by.\n ', tunable_type=float, default=1.0)), 'interaction_score_modifier': TunableList( description= '\n A list of score modifications to interactions (specified by list, \n affordance or tags) to apply when the actor has this autonomy modifier.\n ', tunable=TunableTuple( modifier=Tunable( description= '\n Multiply score by this amount.\n ', tunable_type=float, default=1.0), affordances=TunableSet( description= '\n A list of affordances that will be compared against.\n ', tunable=TunableReference( services.get_instance_manager( sims4.resources.Types.INTERACTION))), affordance_lists=TunableList( description= '\n A list of affordance snippets that will be compared against.\n ', tunable=snippets.TunableAffordanceListReference()), interaction_category_tags=tag.TunableTags( description= '\n This attribute is used to test for affordances that contain any of the tags in this set.\n ', filter_prefixes=('interaction', )))) } def __init__(self, score_multipliers=None, static_commodity_score_multipliers=None, relationship_score_multiplier_with_buff_on_target=None, provided_affordance_compatibility=None, super_affordance_suppression_mode=SuperAffordanceSuppression. AUTONOMOUS_ONLY, suppress_self_affordances=False, suppress_preroll_autonomy=False, super_affordance_suppress_on_add=False, locked_stats=None, locked_stats_autosatisfy_on_unlock=None, decay_modifiers=None, statistic_modifiers=None, skill_tag_modifiers=None, commodities_to_add=(), commodities_value_persist_for_travel=False, only_scored_stats=None, only_scored_static_commodities=None, stat_use_multiplier=None, relationship_multipliers=None, object_tags_that_override_off_lot_autonomy=None, off_lot_autonomy_rule=None, override_convergence_value=None, subject=None, exclusive_si=None, decay_modifier_by_category=None, situation_type_social_score_multiplier=None, transition_from_sit_posture_penalty=None, supress_outside_objects_if_sun_out=None, outside_objects_multiplier=None, interaction_score_modifier=None): self._provided_affordance_compatibility = provided_affordance_compatibility self._super_affordance_suppression_mode = super_affordance_suppression_mode self._suppress_self_affordances = suppress_self_affordances self._super_affordance_suppress_on_add = super_affordance_suppress_on_add self._score_multipliers = score_multipliers self._locked_stats = tuple( set(locked_stats)) if locked_stats is not None else None self.autosatisfy_on_unlock = locked_stats_autosatisfy_on_unlock self._decay_modifiers = decay_modifiers self._decay_modifier_by_category = decay_modifier_by_category self._statistic_modifiers = statistic_modifiers self._relationship_score_multiplier_with_buff_on_target = relationship_score_multiplier_with_buff_on_target self._skill_tag_modifiers = skill_tag_modifiers self._commodities_to_add = commodities_to_add self._commodities_value_persist_for_travel = commodities_value_persist_for_travel self._stat_use_multiplier = stat_use_multiplier self._relationship_multipliers = relationship_multipliers self._object_tags_that_override_off_lot_autonomy = object_tags_that_override_off_lot_autonomy self._off_lot_autonomy_rule = off_lot_autonomy_rule self._subject = subject self._override_convergence_value = override_convergence_value self._exclusive_si = exclusive_si self.suppress_preroll_autonomy = suppress_preroll_autonomy self._situation_type_social_score_multiplier = situation_type_social_score_multiplier self.transition_from_sit_posture_penalty = transition_from_sit_posture_penalty self.supress_outside_objects = supress_outside_objects_if_sun_out self.outside_objects_multiplier = outside_objects_multiplier self._interaction_score_modifier = interaction_score_modifier self._skill_tag_modifiers = {} if skill_tag_modifiers: for (skill_tag, skill_tag_modifier) in skill_tag_modifiers.items(): skill_modifier = SkillTagMultiplier( skill_tag_modifier, StatisticChangeDirection.INCREASE) self._skill_tag_modifiers[skill_tag] = skill_modifier if static_commodity_score_multipliers: if self._score_multipliers is not None: self._score_multipliers = FrozenAttributeDict( self._score_multipliers, dict(static_commodity_score_multipliers)) else: self._score_multipliers = static_commodity_score_multipliers self._static_commodity_score_multipliers = static_commodity_score_multipliers self._only_scored_stat_types = None if only_scored_stats is not None: self._only_scored_stat_types = [] self._only_scored_stat_types.extend(only_scored_stats) if only_scored_static_commodities is not None: if self._only_scored_stat_types is None: self._only_scored_stat_types = [] self._only_scored_stat_types.extend(only_scored_static_commodities) super().__init__(GameEffectType.AUTONOMY_MODIFIER) def apply_modifier(self, sim_info): return sim_info.add_statistic_modifier(self) def remove_modifier(self, sim_info, handle): sim_info.remove_statistic_modifier(handle) def __repr__(self): return standard_auto_repr(self) @property def exclusive_si(self): return self._exclusive_si def affordance_suppressed(self, sim, aop_or_interaction, user_directed=DEFAULT): user_directed = aop_or_interaction.is_user_directed if user_directed is DEFAULT else user_directed if not self._suppress_self_affordances and aop_or_interaction.target is sim: return False affordance = aop_or_interaction.affordance if self._provided_affordance_compatibility is None: return False if user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.AUTONOMOUS_ONLY: return False if not user_directed and self._super_affordance_suppression_mode == SuperAffordanceSuppression.USER_DIRECTED: return False if self._provided_affordance_compatibility(affordance): return False elif self.ALWAYS_WHITELISTED_AFFORDANCES(affordance): return False return True def locked_stats_gen(self): if self._locked_stats is not None: yield from self._locked_stats def get_score_multiplier(self, stat_type): if self._score_multipliers is not None and stat_type in self._score_multipliers: return self._score_multipliers[stat_type] return 1 def get_stat_multiplier(self, stat_type, participant_type): if self._stat_use_multiplier is None: return 1 elif self._subject == participant_type and stat_type in self._stat_use_multiplier: return self._stat_use_multiplier[stat_type].multiplier return 1 @property def subject(self): return self._subject @property def statistic_modifiers(self): return self._statistic_modifiers @property def statistic_multipliers(self): return self._stat_use_multiplier @property def relationship_score_multiplier_with_buff_on_target(self): return self._relationship_score_multiplier_with_buff_on_target @property def situation_type_social_score_multiplier(self): return self._situation_type_social_score_multiplier @property def relationship_multipliers(self): return self._relationship_multipliers @property def decay_modifiers(self): return self._decay_modifiers @property def decay_modifier_by_category(self): return self._decay_modifier_by_category @property def skill_tag_modifiers(self): return self._skill_tag_modifiers @property def commodities_to_add(self): return self._commodities_to_add @property def commodities_value_persist_for_travel(self): return self._commodities_value_persist_for_travel @property def override_convergence(self): return self._override_convergence_value def is_locked(self, stat_type): if self._locked_stats is None: return False return stat_type in self._locked_stats def is_scored(self, stat_type): if self._only_scored_stat_types is None or stat_type in self._only_scored_stat_types: return True return False @property def object_tags_that_override_off_lot_autonomy(self): return self._object_tags_that_override_off_lot_autonomy @property def off_lot_autonomy_rule(self): return self._off_lot_autonomy_rule @property def super_affordance_suppress_on_add(self): return self._super_affordance_suppress_on_add @property def interaction_score_modifier(self): return self._interaction_score_modifier
class Trait(HasTunableReference, SuperAffordanceProviderMixin, TargetSuperAffordanceProviderMixin, HasTunableLodMixin, MixerActorMixin, MixerProviderMixin, metaclass=HashedTunedInstanceMetaclass, manager=services.trait_manager()): EQUIP_SLOT_NUMBER_MAP = TunableMapping( description= '\n The number of personality traits available to Sims of specific ages.\n ', key_type=TunableEnumEntry( description="\n The Sim's age.\n ", tunable_type=sim_info_types.Age, default=sim_info_types.Age.YOUNGADULT), value_type=Tunable( description= '\n The number of personality traits available to a Sim of the specified\n age.\n ', tunable_type=int, default=3), key_name='Age', value_name='Slot Number') PERSONALITY_TRAIT_TAG = TunableEnumEntry( description= '\n The tag that marks a trait as a personality trait.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID, )) DAY_NIGHT_TRACKING_BUFF_TAG = TunableEnumWithFilter( description= '\n The tag that marks buffs as opting in to Day Night Tracking on traits..\n ', tunable_type=tag.Tag, filter_prefixes=['buff'], default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID, )) INSTANCE_TUNABLES = { 'trait_type': TunableEnumEntry( description='\n The type of the trait.\n ', tunable_type=TraitType, default=TraitType.PERSONALITY, export_modes=ExportModes.All, tuning_group=GroupNames.APPEARANCE), 'display_name': TunableLocalizedStringFactory( description= "\n The trait's display name. This string is provided with the owning\n Sim as its only token.\n ", allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.APPEARANCE), 'display_name_gender_neutral': TunableLocalizedString( description= "\n The trait's gender-neutral display name. This string is not provided\n any tokens, and thus can't rely on context to properly form\n masculine and feminine forms.\n ", allow_none=True, tuning_group=GroupNames.APPEARANCE), 'trait_description': TunableLocalizedStringFactory( description="\n The trait's description.\n ", allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.APPEARANCE), 'trait_origin_description': TunableLocalizedString( description= "\n A description of how the Sim obtained this trait. Can be overloaded\n for other uses in certain cases:\n - When the trait type is AGENT this string is the name of the \n agency's Trade type and will be provided with the owning sim \n as its token.\n - When the trait type is HIDDEN and the trait is used by the CAS\n STORIES flow, this can be used as a secondary description in \n the CAS Stories UI. If this trait is tagged as a CAREER CAS \n stories trait, this description will be used to explain which \n skills are also granted with this career.\n ", allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.APPEARANCE), 'icon': TunableResourceKey( description="\n The trait's icon.\n ", allow_none=True, resource_types=CompoundTypes.IMAGE, export_modes=ExportModes.All, tuning_group=GroupNames.APPEARANCE), 'pie_menu_icon': TunableResourceKey( description= "\n The trait's pie menu icon.\n ", resource_types=CompoundTypes.IMAGE, default=None, allow_none=True, tuning_group=GroupNames.APPEARANCE), 'trait_asm_overrides': TunableTuple( description= '\n Tunables that will specify if a Trait will add any parameters\n to the Sim and how it will affect their boundary conditions.\n ', param_type=OptionalTunable( description= '\n Define if this trait is parameterized as an on/off value or as\n part of an enumeration.\n ', tunable=Tunable( description= '\n The name of the parameter enumeration. For example, if this\n value is tailType, then the tailType actor parameter is set\n to the value specified in param_value, for this Sim.\n ', tunable_type=str, default=None), disabled_name='boolean', enabled_name='enum'), trait_asm_param=Tunable( description= "\n The ASM parameter for this trait. If unset, it will be auto-\n generated depending on the instance name (e.g. 'trait_Clumsy').\n ", tunable_type=str, default=None), consider_for_boundary_conditions=Tunable( description= '\n If enabled the trait_asm_param will be considered when a Sim\n is building the goals and validating against its boundary\n conditions.\n This should ONLY be enabled, if we need this parameter for\n cases like a posture transition, or boundary specific cases. \n On regular cases like an animation outcome, this is not needed.\n i.e. Vampire trait has an isVampire parameter set to True, so\n when animatin out of the coffin it does different get in/out \n animations. When this is enabled, isVampire will be set to \n False for every other Sim.\n ', tunable_type=bool, default=False), tuning_group=GroupNames.ANIMATION), 'ages': TunableSet( description= '\n The allowed ages for this trait. If no ages are specified, then all\n ages are considered valid.\n ', tunable=TunableEnumEntry(tunable_type=Age, default=None, export_modes=ExportModes.All), tuning_group=GroupNames.AVAILABILITY), 'genders': TunableSet( description= '\n The allowed genders for this trait. If no genders are specified,\n then all genders are considered valid.\n ', tunable=TunableEnumEntry(tunable_type=Gender, default=None, export_modes=ExportModes.All), tuning_group=GroupNames.AVAILABILITY), 'species': TunableSet( description= '\n The allowed species for this trait. If not species are specified,\n then all species are considered valid.\n ', tunable=TunableEnumEntry(tunable_type=Species, default=Species.HUMAN, invalid_enums=(Species.INVALID, ), export_modes=ExportModes.All), tuning_group=GroupNames.AVAILABILITY), 'conflicting_traits': TunableList( description= '\n Conflicting traits for this trait. If the Sim has any of the\n specified traits, then they are not allowed to be equipped with this\n one.\n \n e.g.\n Family Oriented conflicts with Hates Children, and vice-versa.\n ', tunable=TunableReference(manager=services.trait_manager(), pack_safe=True), export_modes=ExportModes.All, tuning_group=GroupNames.AVAILABILITY), 'is_npc_only': Tunable( description= '\n If checked, this trait will get removed from Sims that have a home\n when the zone is loaded or whenever they switch to a household that\n has a home zone.\n ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'cas_selected_icon': TunableResourceKey( description= '\n Icon to be displayed in CAS when this trait has already been applied\n to a Sim.\n ', resource_types=CompoundTypes.IMAGE, default=None, allow_none=True, export_modes=(ExportModes.ClientBinary, ), tuning_group=GroupNames.CAS), 'cas_idle_asm_key': TunableInteractionAsmResourceKey( description= '\n The ASM to use for the CAS idle.\n ', default=None, allow_none=True, category='asm', export_modes=ExportModes.All, tuning_group=GroupNames.CAS), 'cas_idle_asm_state': Tunable( description= '\n The state to play for the CAS idle.\n ', tunable_type=str, default=None, source_location='cas_idle_asm_key', source_query=SourceQueries.ASMState, export_modes=ExportModes.All, tuning_group=GroupNames.CAS), 'cas_trait_asm_param': Tunable( description= '\n The ASM parameter for this trait for use with CAS ASM state machine,\n driven by selection of this Trait, i.e. when a player selects the a\n romantic trait, the Flirty ASM is given to the state machine to\n play. The name tuned here must match the animation state name\n parameter expected in Swing.\n ', tunable_type=str, default=None, export_modes=ExportModes.All, tuning_group=GroupNames.CAS), 'tags': TunableList( description= "\n The associated categories of the trait. Need to distinguish among\n 'Personality Traits', 'Achievement Traits' and 'Walkstyle\n Traits'.\n ", tunable=TunableEnumEntry(tunable_type=tag.Tag, default=tag.Tag.INVALID), export_modes=ExportModes.All, tuning_group=GroupNames.CAS), 'sim_info_fixup_actions': TunableList( description= '\n A list of fixup actions which will be performed on a sim_info with\n this trait when it is loaded.\n ', tunable=TunableVariant( career_fixup_action=_SimInfoCareerFixupAction.TunableFactory( description= '\n A fix up action to set a career with a specific level.\n ' ), skill_fixup_action=_SimInfoSkillFixupAction.TunableFactory( description= '\n A fix up action to set a skill with a specific level.\n ' ), unlock_fixup_action=_SimInfoUnlockFixupAction.TunableFactory( description= '\n A fix up action to unlock certain things for a Sim\n ' ), perk_fixup_action=_SimInfoPerkFixupAction.TunableFactory( description= '\n A fix up action to grant perks to a Sim. It checks perk required\n unlock tuning and unlocks prerequisite perks first.\n ' ), default='career_fixup_action'), tuning_group=GroupNames.CAS), 'sim_info_fixup_actions_timing': TunableEnumEntry( description= "\n This is DEPRECATED, don't tune this field. We usually don't do trait-based\n fixup unless it's related to CAS stories. We keep this field only for legacy\n support reason.\n \n This is mostly to optimize performance when applying fix-ups to\n a Sim. We ideally would not like to spend time scanning every Sim \n on every load to see if they need fixups. Please be sure you \n consult a GPE whenever you are creating fixup tuning.\n ", tunable_type=SimInfoFixupActionTiming, default=SimInfoFixupActionTiming.ON_FIRST_SIMINFO_LOAD, tuning_group=GroupNames.DEPRECATED, deprecated=True), 'teleport_style_interaction_to_inject': TunableReference( description= '\n When this trait is added to a Sim, if a teleport style interaction\n is specified, any time another interaction runs, we may run this\n teleport style interaction to shorten or replace the route to the \n target.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION), class_restrictions=('TeleportStyleSuperInteraction', ), allow_none=True, tuning_group=GroupNames.SPECIAL_CASES), 'interactions': OptionalTunable( description= '\n Mixer interactions that are available to Sims equipped with this\n trait.\n ', tunable=ContentSet.TunableFactory(locked_args={ 'phase_affordances': frozendict(), 'phase_tuning': None })), 'buffs_add_on_spawn_only': Tunable( description= '\n If unchecked, buffs are added to the Sim as soon as this trait is\n added. If checked, buffs will be added only when the Sim is\n instantiated and removed when the Sim uninstantiates.\n \n General guidelines: If the buffs only matter to Sims, for example\n buffs that alter autonomy behavior or walkstyle, this should be\n checked.\n ', tunable_type=bool, default=True), 'buffs': TunableList( description= '\n Buffs that should be added to the Sim whenever this trait is\n equipped.\n ', tunable=TunableBuffReference(pack_safe=True), unique_entries=True), 'buffs_proximity': TunableList( description= '\n Proximity buffs that are active when this trait is equipped.\n ', tunable=TunableReference(manager=services.buff_manager())), 'buff_replacements': TunableMapping( description= '\n A mapping of buff replacement. If Sim has this trait on, whenever he\n get the buff tuned in the key of the mapping, it will get replaced\n by the value of the mapping.\n ', key_type=TunableReference( description= '\n Buff that will get replaced to apply on Sim by this trait.\n ', manager=services.buff_manager(), reload_dependent=True, pack_safe=True), value_type=TunableTuple( description= '\n Data specific to this buff replacement.\n ', buff_type=TunableReference( description= '\n Buff used to replace the buff tuned as key.\n ', manager=services.buff_manager(), reload_dependent=True, pack_safe=True), buff_reason=OptionalTunable( description= '\n If enabled, override the buff reason.\n ', tunable=TunableLocalizedString( description= '\n The overridden buff reason.\n ' )), buff_replacement_priority=TunableEnumEntry( description= "\n The priority of this buff replacement, relative to other\n replacements. Tune this to be a higher value if you want\n this replacement to take precedence.\n \n e.g.\n (NORMAL) trait_HatesChildren (buff_FirstTrimester -> \n buff_FirstTrimester_HatesChildren)\n (HIGH) trait_Male (buff_FirstTrimester -> \n buff_FirstTrimester_Male)\n \n In this case, both traits have overrides on the pregnancy\n buffs. However, we don't want males impregnated by aliens\n that happen to hate children to lose their alien-specific\n buffs. Therefore we tune the male replacement at a higher\n priority.\n ", tunable_type=TraitBuffReplacementPriority, default=TraitBuffReplacementPriority.NORMAL))), 'excluded_mood_types': TunableList( TunableReference( description= '\n List of moods that are prevented by having this trait.\n ', manager=services.mood_manager())), 'outfit_replacements': TunableMapping( description= "\n A mapping of outfit replacements. If the Sim has this trait, outfit\n change requests are intercepted to produce the tuned result. If\n multiple traits with outfit replacements exist, the behavior is\n undefined.\n \n Tuning 'Invalid' as a key acts as a fallback and applies to all\n reasons.\n \n Tuning 'Invalid' as a value keeps a Sim in their current outfit.\n ", key_type=TunableEnumEntry(tunable_type=OutfitChangeReason, default=OutfitChangeReason.Invalid), value_type=TunableEnumEntry(tunable_type=OutfitChangeReason, default=OutfitChangeReason.Invalid)), 'disable_aging': OptionalTunable( description= '\n If enabled, aging out of specific ages can be disabled.\n ', tunable=TunableTuple( description= '\n The tuning that disables aging out of specific age groups.\n ', allowed_ages=TunableSet( description= '\n A list of ages that the Sim CAN age out of. If an age is in\n this list then the Sim is allowed to age out of it. If an\n age is not in this list than a Sim is not allowed to age out\n of it. For example, if the list only contains Child and\n Teen, then a Child Sim would be able to age up to Teen and\n a Teen Sim would be able to age up to Young Adult. But, a\n Young Adult, Adult, or Elder Sim would not be able to age\n up.\n ', tunable=TunableEnumEntry(Age, default=Age.ADULT)), tooltip=OptionalTunable( description= '\n When enabled, this tooltip will be displayed in the aging\n progress bar when aging is disabled because of the trait.\n ', tunable=TunableLocalizedStringFactory( description= '\n The string that displays in the aging UI when aging up\n is disabled due to the trait.\n ' ))), tuning_group=GroupNames.SPECIAL_CASES), 'can_die': Tunable( description= '\n When set, Sims with this trait are allowed to die. When unset, Sims\n are prevented from dying.\n ', tunable_type=bool, default=True, tuning_group=GroupNames.SPECIAL_CASES), 'culling_behavior': TunableVariant( description= '\n The culling behavior of a Sim with this trait.\n ', default_behavior=CullingBehaviorDefault.TunableFactory(), immune_to_culling=CullingBehaviorImmune.TunableFactory(), importance_as_npc_score=CullingBehaviorImportanceAsNpc. TunableFactory(), default='default_behavior', tuning_group=GroupNames.SPECIAL_CASES), 'always_send_test_event_on_add': Tunable( description= '\n If checked, will send out a test event when added to a trait\n tracker even if the receiving sim is hidden or not instanced.\n ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), 'voice_effect': OptionalTunable( description= '\n The voice effect of a Sim with this trait. This is prioritized\n against other traits with voice effects.\n \n The Sim may only have one voice effect at a time.\n ', tunable=VoiceEffectRequest.TunableFactory()), 'plumbbob_override': OptionalTunable( description= '\n If enabled, allows a new plumbbob model to be used when a Sim has\n this occult type.\n ', tunable=PlumbbobOverrideRequest.TunableFactory()), 'vfx_mask': OptionalTunable( description= '\n If enabled when this trait is added the masks will be applied to\n the Sim affecting the visibility of specific VFX.\n Example: TRAIT_CHILDREN will provide a mask MASK_CHILDREN which \n the monster battle object will only display VFX for any Sim \n using that mask.\n ', tunable=TunableEnumFlags( description= "\n Mask that will be added to the Sim's mask when the trait is\n added.\n ", enum_type=VFXMask), enabled_name='apply_vfx_mask', disabled_name='no_vfx_mask'), 'day_night_tracking': OptionalTunable( description= "\n If enabled, allows this trait to track various aspects of day and\n night via buffs on the owning Sim.\n \n For example, if this is enabled and the Sunlight Buff is tuned with\n buffs, the Sim will get the buffs added every time they're in\n sunlight and removed when they're no longer in sunlight.\n ", tunable=DayNightTracking.TunableFactory()), 'persistable': Tunable( description= '\n If checked then this trait will be saved onto the sim. If\n unchecked then the trait will not be saved.\n Example unchecking:\n Traits that are applied for the sim being in the region.\n ', tunable_type=bool, default=True), 'initial_commodities': TunableSet( description= '\n A list of commodities that will be added to a sim on load, if the\n sim has this trait.\n \n If a given commodity is also blacklisted by another trait that the\n sim also has, it will NOT be added.\n \n Example:\n Adult Age Trait adds Hunger.\n Vampire Trait blacklists Hunger.\n Hunger will not be added.\n ', tunable=Commodity.TunableReference(pack_safe=True)), 'initial_commodities_blacklist': TunableSet( description= "\n A list of commodities that will be prevented from being\n added to a sim that has this trait.\n \n This always takes priority over any commodities listed in any\n trait's initial_commodities.\n \n Example:\n Adult Age Trait adds Hunger.\n Vampire Trait blacklists Hunger.\n Hunger will not be added.\n ", tunable=Commodity.TunableReference(pack_safe=True)), 'ui_commodity_sort_override': OptionalTunable( description= '\n Optional list of commodities to override the default UI sort order.\n ', tunable=TunableList( description= '\n The position of the commodity in this list represents the sort order.\n Add all possible combination of traits in the list.\n If we have two traits which have sort override, we will implement\n a priority system to determine which determines which trait sort\n order to use.\n ', tunable=Commodity.TunableReference())), 'ui_category': OptionalTunable( description= '\n If enabled then this trait will be displayed in a specific category\n within the relationship panel if this trait would be displayed\n within that panel.\n ', tunable=TunableEnumEntry( description= '\n The UI trait category that we use to categorize this trait\n within the relationship panel.\n ', tunable_type=TraitUICategory, default=TraitUICategory.PERSONALITY), export_modes=ExportModes.All, enabled_name='ui_trait_category_tag'), 'loot_on_trait_add': OptionalTunable( description= '\n If tuned, this list of loots will be applied when trait is added in game.\n ', tunable=TunableList( description= '\n List of loot to apply on the sim when this trait is added not\n through CAS.\n ', tunable=TunableReference( description= '\n Loot to apply.\n ', manager=services.get_instance_manager( sims4.resources.Types.ACTION), pack_safe=True))), 'npc_leave_lot_interactions': OptionalTunable( description= '\n If enabled, allows tuning a set of Leave Lot and Leave Lot Must Run\n interactions that this trait provides. NPC Sims with this trait will\n use these interactions to leave the lot instead of the defaults.\n ', tunable=TunableTuple( description= '\n Leave Lot Now and Leave Lot Now Must Run interactions.\n ', leave_lot_now_interactions=TunableSet( TunableReference( description= '\n If tuned, the Sim will consider these interaction when trying to run\n any "leave lot" situation.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION), allow_none=False, pack_safe=True)), leave_lot_now_must_run_interactions=TunableSet( TunableReference( description= '\n If tuned, the Sim will consider these interaction when trying to run\n any "leave lot must run" situation.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION), allow_none=False, pack_safe=True)))), 'hide_relationships': Tunable( description= '\n If checked, then any relationships with a Sim who has this trait\n will not be displayed in the UI. This is done by keeping the\n relationship from having any tracks to actually track which keeps\n it out of the UI.\n ', tunable_type=bool, default=False, tuning_group=GroupNames.RELATIONSHIP), 'whim_set': OptionalTunable( description= '\n If enabled then this trait will offer a whim set to the Sim when it\n is active.\n ', tunable=TunableReference( description= '\n A whim set that is active when this trait is active.\n ', manager=services.get_instance_manager( sims4.resources.Types.ASPIRATION), class_restrictions=('ObjectivelessWhimSet', ))), 'allow_from_gallery': Tunable( description= '\n If checked, then this trait is allowed to be transferred over from\n Sims downloaded from the gallery.\n ', tunable_type=bool, default=True, tuning_group=GroupNames.SPECIAL_CASES), 'remove_on_death': Tunable( description= '\n If checked, when a Sim dies this trait will be removed.\n ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), 'build_buy_purchase_tracking': OptionalTunable( description= '\n If enabled, allows this trait to track various build-buy purchases\n via event listening in the trait tracker.\n ', tunable=TunableList( description= '\n Loots to apply to the hamper when clothing pile is being put.\n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.ACTION), class_restrictions=('LootActions', ), pack_safe=True))) } _asm_param_name = None default_trait_params = set() def __repr__(self): return '<Trait:({})>'.format(self.__name__) def __str__(self): return '{}'.format(self.__name__) @classmethod def _tuning_loaded_callback(cls): cls._asm_param_name = cls.trait_asm_overrides.trait_asm_param if cls._asm_param_name is None: cls._asm_param_name = cls.__name__ if cls.trait_asm_overrides.trait_asm_param is not None and cls.trait_asm_overrides.consider_for_boundary_conditions: cls.default_trait_params.add( cls.trait_asm_overrides.trait_asm_param) for (buff, replacement_buff) in cls.buff_replacements.items(): if buff.trait_replacement_buffs is None: buff.trait_replacement_buffs = {} buff.trait_replacement_buffs[cls] = replacement_buff for mood in cls.excluded_mood_types: if mood.excluding_traits is None: mood.excluding_traits = [] mood.excluding_traits.append(cls) @classmethod def _verify_tuning_callback(cls): if cls.display_name: if not cls.display_name_gender_neutral.hash: logger.error( 'Trait {} specifies a display name. It must also specify a gender-neutral display name. These must use different string keys.', cls, owner='BadTuning') if cls.display_name._string_id == cls.display_name_gender_neutral.hash: logger.error( 'Trait {} has the same string tuned for its display name and its gender-neutral display name. These must be different strings for localization.', cls, owner='BadTuning') if cls.day_night_tracking is not None: if not cls.day_night_tracking.sunlight_buffs and not ( not cls.day_night_tracking.shade_buffs and not (not cls.day_night_tracking.day_buffs and not cls.day_night_tracking.night_buffs)): logger.error( 'Trait {} has Day Night Tracking enabled but no buffs are tuned. Either tune buffs or disable the tracking.', cls, owner='BadTuning') else: tracking_buff_tag = Trait.DAY_NIGHT_TRACKING_BUFF_TAG if any( buff for buff in cls.day_night_tracking.sunlight_buffs if not buff.buff_type.has_tag(tracking_buff_tag) ) or (any(buff for buff in cls.day_night_tracking.shade_buffs if not buff.buff_type.has_tag(tracking_buff_tag)) or any(buff for buff in cls.day_night_tracking.day_buffs if not buff.buff_type.has_tag(tracking_buff_tag)) ) or any( buff for buff in cls.day_night_tracking.night_buffs if not buff.buff_type.has_tag(tracking_buff_tag)): logger.error( 'Trait {} has Day Night tracking with an invalid\n buff. All buffs must be tagged with {} in order to be\n used as part of Day Night Tracking. Add these buffs with the\n understanding that, regardless of what system added them, they\n will always be on the Sim when the condition is met (i.e.\n Sunlight Buffs always added with sunlight is out) and they will\n always be removed when the condition is not met. Even if another\n system adds the buff, they will be removed if this trait is\n tuned to do that.\n ', cls, tracking_buff_tag) for buff_reference in cls.buffs: if buff_reference.buff_type.broadcaster is not None: logger.error( 'Trait {} has a buff {} with a broadcaster tuned that will never be removed. This is a potential performance hit, and a GPE should decide whether this is the best place for such.', cls, buff_reference, owner='rmccord') for commodity in cls.initial_commodities: if not commodity.persisted_tuning: logger.error( 'Trait {} has an initial commodity {} that does not have persisted tuning.', cls, commodity) @classproperty def is_personality_trait(cls): return cls.trait_type == TraitType.PERSONALITY @classproperty def is_aspiration_trait(cls): return cls.trait_type == TraitType.ASPIRATION @classproperty def is_gender_option_trait(cls): return cls.trait_type == TraitType.GENDER_OPTIONS @classproperty def is_ghost_trait(cls): return cls.trait_type == TraitType.GHOST @classproperty def is_robot_trait(cls): return cls.trait_type == TraitType.ROBOT @classmethod def is_valid_trait(cls, sim_info_data): if cls.ages and sim_info_data.age not in cls.ages: return False if cls.genders and sim_info_data.gender not in cls.genders: return False elif cls.species and sim_info_data.species not in cls.species: return False return True @classmethod def should_apply_fixup_actions(cls, fixup_source): if cls.sim_info_fixup_actions and cls.sim_info_fixup_actions_timing == fixup_source: if fixup_source != SimInfoFixupActionTiming.ON_FIRST_SIMINFO_LOAD: logger.warn( 'Trait {} has fixup actions not from CAS flow.This should only happen to old saves before EP08', cls, owner='yozhang') return True return False @classmethod def apply_fixup_actions(cls, sim_info): for fixup_action in cls.sim_info_fixup_actions: fixup_action(sim_info) @classmethod def can_age_up(cls, current_age): if not cls.disable_aging: return True return current_age in cls.disable_aging.allowed_ages @classmethod def is_conflicting(cls, trait): if trait is None: return False if cls.conflicting_traits and trait in cls.conflicting_traits: return True elif trait.conflicting_traits and cls in trait.conflicting_traits: return True return False @classmethod def get_outfit_change_reason(cls, outfit_change_reason): replaced_reason = cls.outfit_replacements.get( outfit_change_reason if outfit_change_reason is not None else OutfitChangeReason.Invalid) if replaced_reason is not None: return replaced_reason elif outfit_change_reason is not None: replaced_reason = cls.outfit_replacements.get( OutfitChangeReason.Invalid) if replaced_reason is not None: return replaced_reason return outfit_change_reason @classmethod def get_teleport_style_interaction_to_inject(cls): return cls.teleport_style_interaction_to_inject
class GardeningTuning: __qualname__ = 'GardeningTuning' class _Inheritance(HasTunableSingletonFactory, AutoFactoryInit): __qualname__ = 'GardeningTuning._Inheritance' @staticmethod def _verify_tunable_callback(instance_class, tunable_name, source, value): if not value.inherit_from_mother and not value.inherit_from_father: raise ValueError('Must inherit from at least one parent.') FACTORY_TUNABLES = { 'inherited_state': ObjectState.TunableReference( description= '\n Controls the state value that will be inherited by offspring.\n ' ), 'inherit_from_mother': Tunable( description= "\n If checked, the mother's (root stock's) state value and fitness will\n be considered when deciding what state value the child should\n inherit.\n ", tunable_type=bool, needs_tuning=True, default=True), 'inherit_from_father': Tunable( description= "\n If checked, the father's (a spliced fruit's genes) state value and\n fitness will be considered when deciding what state value the child\n should inherit. In the case a plant is spawning the type of fruit\n it grew from, this will be the same as the mother's contribution.\n ", tunable_type=bool, needs_tuning=True, default=True), 'inheritance_chance': TunablePercent( description= "\n The chance the offspring will inherit this state value from its\n parents at all. If the check doesn't pass, the default value for\n the state will be used.\n ", default=1), 'verify_tunable_callback': _verify_tunable_callback } def get_inherited_value(self, mother, father): raise NotImplementedError() class LamarkianInheritance(_Inheritance): __qualname__ = 'GardeningTuning.LamarkianInheritance' CALCULATIONS = { ParentalContribution.MAXIMUM: max, ParentalContribution.MINIMUM: min, ParentalContribution.AVERAGE: lambda l: sum(l) / len(l) } FACTORY_TUNABLES = { '_calculation': TunableEnumEntry(ParentalContribution, ParentalContribution.MINIMUM, needs_tuning=True), 'fitness_stat': Statistic.TunableReference( description= '\n This statistic is used as the x-value on the fitness curve.\n ' ), 'fitness_curve_maximum': TunableCurve( description= '\n This curve maps fitness stat values to maximum state changes.\n ' ), 'fitness_curve_minimum': TunableCurve( description= '\n This curve maps fitness stat values to minimum state changes.\n ' ) } @property def calculation(self): return self.CALCULATIONS[self._calculation] def get_inherited_value(self, mother, father): values = [] indeces = [] if self.inherit_from_mother: values.append(mother.get_stat_value(self.fitness_stat)) inherited_state_value = mother.get_state(self.inherited_state) indeces.append( self.inherited_state.values.index(inherited_state_value)) if self.inherit_from_father: values.append(father.get_stat_value(self.fitness_stat)) inherited_state_value = father.get_state(self.inherited_state) indeces.append( self.inherited_state.values.index(inherited_state_value)) fitness_value = self.calculation(values) max_delta = self.fitness_curve_maximum.get(fitness_value) min_delta = self.fitness_curve_minimum.get(fitness_value) delta = round(sims4.random.uniform(min_delta, max_delta)) index = self.calculation(indeces) + delta index = sims4.math.clamp(0, index, len(self.inherited_state.values)) return self.inherited_state.values[index] class MendelianInheritance(_Inheritance): __qualname__ = 'GardeningTuning.MendelianInheritance' def get_inherited_value(self, mother, father): possibilities = set() if self.inherit_from_mother: possibilities.add(mother.get_state(self.inherited_state)) if self.inherit_from_father: possibilities.add(father.get_state(self.inherited_state)) if not possibilities: possibilities.update(self.inherited_state.values) return sims4.random.random.choice(list(possibilities)) PLANT_GENETICS = TunableList( description= '\n Instructions for transmitting traits to later generations. These are\n applied each time a fruit is generated by a plant.\n ', tunable=TunableVariant( lamarkian_inheritance=LamarkianInheritance.TunableFactory(), mendelian_inheritance=MendelianInheritance.TunableFactory())) @classproperty def INHERITED_STATES(cls): return [ inheritance.inherited_state for inheritance in cls.PLANT_GENETICS ] SPONTANEOUS_GERMINATION_COMMODITY = Commodity.TunableReference() SPONTANEOUS_GERMINATION_COMMODITY_VARIANCE = TunableRange( description= '\n Max variance to apply when the spawn commodity is reset. This helps\n plants all not to sprout from seeds at the same time.\n ', tunable_type=int, default=10, minimum=0) SCALE_COMMODITY = Commodity.TunableReference() SCALE_VARIANCE = TunableInterval( description= "\n Control how much the size of child fruit can vary from its father's\n size.\n ", tunable_type=float, default_lower=0.8, default_upper=1.2) SCALE_INFO_NAME = TunableLocalizedString() SCALE_INFO_UNIT_FORMATS = TunableMapping( key_type=TunableEnumEntry(MassUnit, MassUnit.GRAMS), value_type=TunableLocalizedStringFactory()) STATES_WITH_STATUS_ICONS = TunableList( description= '\n The list of object states whose icons will be reflected in the gardening\n tooltip on each plant details. These refer to the sub icons represented\n on the tooltip.\n ', tunable=ObjectState.TunableReference()) STATE_MAIN_ICON = ObjectState.TunableReference( description= "\n Object state which will represent the main icon of the gardening\n tooltip. This state doesn't get modified by interactions but its \n calculated whenever the tooltip will be generated, using the states\n tuned on STATES_WITH_STATUS_ICONS.\n " ) STATES_WITH_ADDITIONAL_INFO = TunableList( description= "\n The list of object states whose name and state value name will be\n reflected in the gardening tooltip's additional information section.\n ", tunable=ObjectState.TunableReference()) SHOOT_DEFINITION = TunableReference( description= '\n The object definition to use when creating Shoot objects for the\n splicing system.\n ', manager=services.definition_manager()) SHOOT_STATE_VALUE = ObjectStateValue.TunableReference( description= "\n The state value all Shoot objects will have. Remember to add this as\n the default value for a state in the Shoot's state component tuning.\n " ) WITHERED_STATE_VALUE = ObjectStateValue.TunableReference( description= '\n The state value when a plant is withered.\n ') DISABLE_DETAILS_STATE_VALUES = TunableList( description= '\n List of object state values where the gardening details should not \n be shown. This is for cases like Wild plants where we dont want\n details that will not be used.\n ', tunable=ObjectStateValue.TunableReference( description= '\n The state that will disable the plant additional information.\n ' )) DISABLE_TOOLTIP_STATE_VALUES = TunableList( description= '\n List of object state values where the gardening object will disable \n its tooltip.\n ', tunable=ObjectStateValue.TunableReference( description= '\n The state that will disable the object tooltip.\n ' )) SPLICED_PLANT_NAME = TunableLocalizedStringFactory( description= '\n Localized name to be set when a plant is spliced. \n ' ) SPLICED_PLANT_DESCRIPTION = TunableLocalizedStringFactory( description= '\n Localized factory that will receive two fruit names and concatenate \n them using and in between.\n e.g. {0.String} and {1.String} \n ' ) SPLICED_STATE_VALUE = ObjectStateValue.TunableReference( description= '\n The state that will mean this plant has been already spliced. \n ' ) PICKUP_STATE_MAPPING = TunableMapping( description= '\n Mapping that will set a state that should be set on the fruit when \n its picked up, depending on a state fruit is currently in.\n ', key_type=ObjectStateValue.TunableReference(), value_type=ObjectStateValue.TunableReference()) GARDENING_SLOT = TunableReference( description= '\n Slot type used by the gardening system to create its fruit.\n ', manager=services.get_instance_manager(sims4.resources.Types.SLOT_TYPE)) @classmethod def get_spliced_description(cls, fruit_names): if len(fruit_names) < 2: return sims4.localization.LocalizationHelperTuning.get_raw_text( *fruit_names) if len(fruit_names) > 2: last_fruit = fruit_names.pop() fruit_names_loc = sims4.localization.LocalizationHelperTuning.get_comma_separated_list( *fruit_names) else: fruit_names_loc = fruit_names[0] last_fruit = fruit_names[1] return cls.SPLICED_PLANT_DESCRIPTION(fruit_names_loc, last_fruit) @classmethod def is_spliced(cls, obj): if obj.has_state(cls.SPLICED_STATE_VALUE.state) and obj.get_state( cls.SPLICED_STATE_VALUE.state) == cls.SPLICED_STATE_VALUE: return True return False @classmethod def is_shoot(cls, obj): if obj.has_state(cls.SHOOT_STATE_VALUE.state) and obj.get_state( cls.SHOOT_STATE_VALUE.state) == cls.SHOOT_STATE_VALUE: return True return False SPLICING_FAMILY_INFO_NAME = TunableLocalizedString( description= '\n The label to use in the UI for splicing information.\n ' ) SPLICING_FAMILY_NAMES = TunableMapping( description= '\n The names to use for each splicing family when displayed in the UI.\n ', key_type=TunableEnumEntry(SplicingFamily, SplicingFamily.DEFAULT), value_type=TunableLocalizedString()) ALWAYS_GERMINATE_IF_NOT_SPAWNED_STATE = ObjectStateValue.TunableReference( description= '\n If the specified state value is active on the gardening object, it will\n have a 100% germination chance for when it is placed in the world in\n any way other than through a spawner.\n ' ) QUALITY_STATE_VALUE = ObjectState.TunableReference( description= '\n The quality state all gardening plants will have. \n ' )
class GardeningTuning: INHERITED_STATE = ObjectState.TunableReference( description= '\n Controls the state value that will be inherited by offspring.\n ' ) SPONTANEOUS_GERMINATION_COMMODITY = Commodity.TunableReference() SPONTANEOUS_GERMINATION_COMMODITY_VARIANCE = TunableRange( description= '\n Max variance to apply when the spawn commodity is reset. This helps\n plants all not to sprout from seeds at the same time.\n ', tunable_type=int, default=10, minimum=0) SCALE_COMMODITY = Commodity.TunableReference() SCALE_VARIANCE = TunableInterval( description= "\n Control how much the size of child fruit can vary from its father's\n size.\n ", tunable_type=float, default_lower=0.8, default_upper=1.2) EVOLUTION_STATE = ObjectState.TunableReference( description= '\n Object state which will represent the icon behind the main icon of \n the gardening tooltip. This should be tied to the evolution state\n of gardening objects.\n ' ) SHOOT_DESCRIPTION_STRING = TunableLocalizedString( description= "\n Text that will be given to a shoot description following ':' to its \n fruit name.\n e.g. 'Shoot taken from: Apple'\n " ) DISABLE_DETAILS_STATE_VALUES = TunableList( description= '\n List of object state values where the gardening details should not \n be shown. This is for cases like Wild plants where we dont want\n details that will not be used.\n ', tunable=ObjectStateValue.TunableReference( description= '\n The state that will disable the plant additional information.\n ' )) DISABLE_TOOLTIP_STATE_VALUES = TunableList( description= '\n List of object state values where the gardening object will disable \n its tooltip.\n ', tunable=ObjectStateValue.TunableReference( description= '\n The state that will disable the object tooltip.\n ' )) SPLICED_PLANT_NAME = TunableLocalizedStringFactory( description= '\n Localized name to be set when a plant is spliced. \n ' ) SPLICED_STATE_VALUE = ObjectStateValue.TunableReference( description= '\n The state that will mean this plant has been already spliced. \n ' ) PICKUP_STATE_MAPPING = TunableMapping( description= '\n Mapping that will set a state that should be set on the fruit when \n its picked up, depending on a state fruit is currently in.\n ', key_type=ObjectStateValue.TunableReference(), value_type=ObjectStateValue.TunableReference()) GARDENING_SLOT = TunableReference( description= '\n Slot type used by the gardening system to create its fruit.\n ', manager=services.get_instance_manager(sims4.resources.Types.SLOT_TYPE)) GERMINATE_FAILURE_NOTIFICATION = UiDialogNotification.TunableFactory( description= '\n Notification that will tell the player that the plant has failed to\n germinate.\n ' ) UNIDENTIFIED_STATE_VALUE = ObjectStateValue.TunableReference( description= '\n The state value all unidentified plants will have. Remember to add this\n as the default value for a state in the identifiable plants state\n component tuning.\n ' ) SEASONALITY_STATE = ObjectState.TunablePackSafeReference( description= "\n A reference to the state that determines whether a plant is\n Dormant/Indoors/In Season/Out of Season.\n \n The state value's display data is used in the UI tooltip for the plant.\n " ) SEASONALITY_IN_SEASON_STATE_VALUE = ObjectStateValue.TunablePackSafeReference( description= '\n A reference to the state value that marks a plant as being In Season.\n \n This state value is determined to detect seasonality.\n ' ) SEASONALITY_ALL_SEASONS_TEXT = TunableLocalizedString( description= '\n The seasons text to display if the plant has no seasonality.\n ' ) PLANT_SEASONALITY_TEXT = TunableLocalizedStringFactory( description= "\n The text to display for the plant's seasonality.\n e.g.:\n Seasonality:\n{0.String}\n " ) FRUIT_STATES = TunableMapping( description= '\n A mapping that defines which states on plants support fruits, and the\n behavior when plants transition out of these states.\n ', key_type=ObjectState.TunableReference(pack_safe=True), value_type=TunableTuple( states=TunableList( description= '\n The list of states that supports fruit. If the object changes\n state (for the specified state track) and the new value is not\n in this list, the fruit is destroyed according to the specified\n rule.\n ', tunable=ObjectStateValue.TunableReference(pack_safe=True), unique_entries=True), behavior= TunableVariant( description= "\n Define the fruit's behavior when plants exit a state that\n supports fruit.\n ", rot=TunablePercent( description= '\n Define the chance that the fruit falls and rots, as opposed\n to just being destroyed.\n ', default=5), locked_args={'destroy': None}, default='destroy'))) FRUIT_DECAY_COMMODITY = TunableReference( description= '\n The commodity that defines fruit decay (e.g. rotten/ripe).\n ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)) FRUIT_DECAY_COMMODITY_DROPPED_VALUE = Tunable( description= '\n Value to set the Fruit Decay Commodity on a harvestable that has\n been dropped from a plant during a seasonal transition.\n ', tunable_type=int, default=10) SPAWN_WEIGHTS = TunableMapping( description= "\n A fruit's chance to be spawned in a multi-fruit plant (e.g. via\n splicing/grafting) is determined by its rarity.\n \n The weight is meant to curb the chance of spawning rarer fruits growing\n on more common plants. It would never reduce the chance of the root\n stock from spawning on its original plant.\n \n e.g.\n A common Apple on a rare Pomegranate tree spawns at a 1:1 ratio.\n A rare Pomegranate on a common Apple tree spawns at a 1:5 ratio.\n ", key_type=TunableEnumEntry(tunable_type=ObjectCollectionRarity, default=ObjectCollectionRarity.COMMON), value_type=TunableRange(tunable_type=int, default=1, minimum=0)) EXCLUSIVE_FRUITS = TunableSet( description= '\n A set of fruits, which, when added onto a plant, can restrict\n what other fruits the plant produces to this set of fruits. \n This is done by adjusting spawn weight of non-exclusive fruits \n on the plant to zero. \n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.OBJECT), pack_safe=True)) VERTICAL_GARDEN_OBJECTS = TunableSet( description='\n A set of Vertical garden objects.\n ', tunable=TunableReference(manager=services.definition_manager(), pack_safe=True)) @classmethod def is_spliced(cls, obj): if obj.has_state(cls.SPLICED_STATE_VALUE.state) and obj.get_state( cls.SPLICED_STATE_VALUE.state) == cls.SPLICED_STATE_VALUE: return True return False @classmethod def is_unidentified(cls, obj): if cls.UNIDENTIFIED_STATE_VALUE is not None and obj.has_state( cls.UNIDENTIFIED_STATE_VALUE.state) and obj.get_state( cls.UNIDENTIFIED_STATE_VALUE.state ) == cls.UNIDENTIFIED_STATE_VALUE: return True return False @classmethod def get_seasonality_text_from_plant(cls, plant_definition): season_component = plant_definition.cls._components.season_aware_component if season_component is not None: seasons = [] season_tuned_values = season_component._tuned_values for (season_type, season_states ) in season_tuned_values.seasonal_state_mapping.items(): if any(s is GardeningTuning.SEASONALITY_IN_SEASON_STATE_VALUE for s in season_states): season = SeasonsTuning.SEASON_TYPE_MAPPING[season_type] seasons.append((season_type, season)) if seasons: return GardeningTuning.PLANT_SEASONALITY_TEXT( LocalizationHelperTuning.get_comma_separated_list(*tuple( season.season_name for (_, season) in sorted(seasons)))) ALWAYS_GERMINATE_IF_NOT_SPAWNED_STATE = ObjectStateValue.TunableReference( description= '\n If the specified state value is active on the gardening object, it will\n have a 100% germination chance for when it is placed in the world in\n any way other than through a spawner.\n ' ) QUALITY_STATE_VALUE = ObjectState.TunableReference( description= '\n The quality state all gardening plants will have. \n ' )